Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Almost a complete rewrite. Adds much more flexibility including supp… #4

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .versions
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ html-tools@1.0.5
htmljs@1.0.5
id-map@1.0.4
jquery@1.11.4
local-test:aldeed:schema-index@1.0.1
logging@1.0.8
mdg:validation-error@0.2.0
meteor@1.1.10
Expand All @@ -42,7 +41,6 @@ retry@1.0.4
routepolicy@1.0.6
spacebars@1.0.7
spacebars-compiler@1.0.7
tinytest@1.0.6
tracker@1.0.9
ui@1.0.8
underscore@1.0.4
Expand Down
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@

# 1.0.0

Initial release. Originally included in the aldeed:collection2 package.
Initial release. Originally included in the aldeed:collection2 package.

# 2.0.0

Substantial rewrite. Support for all mongo index options.
1 change: 1 addition & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
The MIT License (MIT)

Copyright (c) 2015 Eric Dobbertin
Portions Copyright (c) 2016 David Pankros

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
Expand Down
108 changes: 90 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,58 +13,130 @@ In your Meteor app directory, enter:
$ meteor add aldeed:schema-index
```

## Usage
## Migration from 1.0.x to 2.0.x
Version 1.0.x used index definitions exclusively on a collection's simpleschema. Version 2 moves some of the index properties to an external function call (`attachIndex`). Migration from 1.x to 2.x requires modificaiton of the simpleschema and addition of a call to `attachIndex` for each index to create. For example in version 1.0.x:

Use the `index` option to ensure a MongoDB index for a specific field:
```js
var books = new Mongo.Collection('books');
books.attachSchema(new SimpleSchema({
title: {
type: String,
label: 'Title',
max: 200,
index: true
},
author: {
type: String,
label: 'Author'
},
isbn: {
type: String,
label: 'ISBN',
optional: true,
index: 1,
unique: true
},
//...
}));
```

In version 2.0.x:

```js
{
var books = new Mongo.Collection('books');
books.attachSchema(new SimpleSchema({
title: {
type: String,
index: 1
label: 'Title',
max: 200,
index: {
name: 'title',
type:-1
}
},
author: {cccccccblegetujrrtbgfrcncllvketghefdehvhiuln
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's that? :)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like a yubikey OTP

cccccchcdhvtiklejrnuujjbtedbbnllktvgigvtnfrr

:)


type: String,
label: 'Author'
},
isbn: {
type: String,
label: 'ISBN',
optional: true,
index: {name: 'isbn'}
},
//...
}));

books.attachIndex('title'); //uses default index options
books.attachIndex('isbn', {
unique: true,
action: 'rebuild' //<-- drops and recreates the index EVERY TIME you restart.
}
}
);
```

Set to `1` or `true` for an ascending index. Set to `-1` for a descending index. Or you may set this to another type of specific MongoDB index, such as `"2d"`. Indexes work on embedded sub-documents as well.
For now, the index names added to mongo will not match the index names you specify. It is overridden to maintain some compatibility with collection2 due to a limitation in how collection2 determines invalid keys for its invalidKeys() method. Importantly, collection2 will not be able to determine the invalid keys for composite indexes if a constrain violation occurs.


If you have created an index for a field by mistake and you want to remove or change it, set `index` to `false`:
## Usage

Use the `index` option to ensure a MongoDB index for a specific field,or for multiple fields, add the same index to each:

```js
{
"address.street": {
title: {
type: String,
index: false
index: {
name: 'titleIndex',
type: 1
}
}
```
Set to value to `1` for an ascending index. Set to `-1` for a descending index. Or you may set this to another type of specific MongoDB index, such as `"2d"`. Indexes work on embedded sub-documents as well.
Then attach the index to the collection in much the same way as you attach a schema to a collection:

IMPORTANT: If you need to change anything about an index, you must first start the app with `index: false` to drop the old index, and then restart with the correct index properties.
```js
books.attchIndex('titleIndex');
```

If a field has the `unique` option set to `true`, the MongoDB index will be a unique index as well. Then on the server, Collection2 will rely on MongoDB to check uniqueness of your field, which is more efficient than our custom checking.
If you have created an index for a field by mistake and you want to remove or change it, set `action` to `rebuild` or `drop`:

```js
{
"pseudo": {
type: String,
index: true,
books.attchIndex('titleIndex',
{
action: 'drop' //options are: 'rebuild', 'build', or 'drop'
}
);
```

IMPORTANT: If you need to change anything about an index, you must first start the app with `action: 'rebuild'` to drop the old index and recreate the index from scratch.

If an index has the `unique` option set to `true`, the MongoDB index will be a unique index as well. Then on the server, Collection2 will rely on MongoDB to check uniqueness of your field, which is more efficient than our custom checking.

```js
books.attchIndex('pseudo',
{
unique: true
}
}
);
```

For the `unique` option to work, `index` must be `true`, `1`, or `-1`. The error message for uniqueness is very generic. It's best to define your own using `MyCollection.simpleSchema().messages()`. The error type string is "notUnique".
The error message for uniqueness is very generic. It's best to define your own using `MyCollection.simpleSchema().messages()`. The error type string is "notUnique".

Any mongo index option can be passed to `attachIndex` with all the same restrictions that Mongo places on you, except the `name` option. The `name` option is set by the first parameter to `attachIndex`. See the [Mongo Docs](https://docs.mongodb.org/v3.0/reference/method/db.collection.createIndex/#db.collection.createIndex) for a complete list of available index options.

You can use the `sparse` option along with the `index` and `unique` options to tell MongoDB to build a [sparse index](http://docs.mongodb.org/manual/core/index-sparse/#index-type-sparse). By default, MongoDB will only permit one document that lacks the indexed field. By setting the `sparse` option to `true`, the index will only contain entries for documents that have the indexed field. The index skips over any document that is missing the field. This is helpful when indexing on a key in an array of sub-documents. Learn more in the [MongoDB docs](http://docs.mongodb.org/manual/core/index-unique/#unique-index-and-missing-field).

All indexes are built in the background so indexing does *not* block other database queries.
All indexes are built in the background by default so indexing does *not* block other database queries. This can be changed by explicitly setting the `background` option to false, but please use caution when doing so.

## Contributing

Anyone is welcome to contribute. Fork, make and test your changes (`meteor test-packages ./`), and then submit a pull request.

### Major Contributors

@dpankros
@mquandalle

(Add yourself if you should be listed here.)
82 changes: 82 additions & 0 deletions lib/indexData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* IndexData - internal class used to track information about the index as it
* comes in
*/

IndexData = class IndexData {
constructor(name) {
check(name, String);
this._name = name;
this._options = {
background: true //default
};
this._fields = {};
this._action = 'build';
this.c2_compatFix = true;
if (this.c2_compatFix) {
this._idxName = 'c2_';
}
}

get name() {
return this._name;
}

get options() {
if (this.c2_compatFix) {
return _({}).extend(this._options, {name: this._idxName});
} else {
return this._options;
}
}

set options(val) {
if (!val) return;

if (val.action) {//strip out action, if it is present
this._action = val.action;
delete val.action;
}

if (!this.c2_compatFix) {
//name object must be last to override anything passed in
_(this._options).extend(val, {name: this.name});
} else {
_(this._options).extend(val);
}
}

setOptionValue(option, value) {
this.options[option] = value;
}

getOptionValue(option) {
return this.options[option];
}


get fields() {
return this._fields;
}

addFieldIndex(fieldName, fieldType) {
var fieldName = fieldName.replace(/\.\$\./g, ".");
this.fields[fieldName] = fieldType;
if (this.c2_compatFix) {
this._idxName = this._idxName + fieldName;
}
}

get needsBuild() {
return this.action === 'build' || this.action === 'rebuild';
}

get needsDrop() {
return this.action === 'rebuild' || this.action === 'drop';
}

get action() {
return this._action;
}
}

67 changes: 14 additions & 53 deletions lib/indexing.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// Extend the schema options allowed by SimpleSchema
SimpleSchema.extendOptions({
index: Match.Optional(Match.OneOf(Number, String, Boolean)),
unique: Match.Optional(Boolean),
sparse: Match.Optional(Boolean),
index: Match.Optional(Object)
});

// Define validation error messages
Expand All @@ -11,56 +9,19 @@ SimpleSchema.messages({
});

if (Meteor.isServer) {

Mongo.Collection.prototype.attachIndex = function siAttachIndex(name, options) {
check(name, String);

options = options || {};
this._si = this._si || new SchemaIndex();
this._si.addIndexDatum(name).options = options;
this._si.processIndex(name);

};

Collection2.on('schema.attached', function (collection, ss) {
function ensureIndex(index, indexName, unique, sparse) {
Meteor.startup(function () {
collection._collection._ensureIndex(index, {
background: true,
name: indexName,
unique: unique,
sparse: sparse
});
});
}

function dropIndex(indexName) {
Meteor.startup(function () {
try {
collection._collection._dropIndex(indexName);
} catch (err) {
// no index with that name, which is what we want
}
});
}

// Loop over fields definitions and ensure collection indexes (server side only)
_.each(ss.schema(), function(definition, fieldName) {
if ('index' in definition || definition.unique === true) {
var index = {}, indexValue;
// If they specified `unique: true` but not `index`,
// we assume `index: 1` to set up the unique index in mongo
if ('index' in definition) {
indexValue = definition.index;
if (indexValue === true) indexValue = 1;
} else {
indexValue = 1;
}
var indexName = 'c2_' + fieldName;
// In the index object, we want object array keys without the ".$" piece
var idxFieldName = fieldName.replace(/\.\$\./g, ".");
index[idxFieldName] = indexValue;
var unique = !!definition.unique && (indexValue === 1 || indexValue === -1);
var sparse = definition.sparse || false;

// If unique and optional, force sparse to prevent errors
if (!sparse && unique && definition.optional) sparse = true;

if (indexValue === false) {
dropIndex(indexName);
} else {
ensureIndex(index, indexName, unique, sparse);
}
}
});
collection._si = collection._si || new SchemaIndex();
collection._si.attach(collection, ss);
});
}
Loading