Persistence

Massive's lack of entity modeling means it retrieves your data in the form of plain JavaScript objects and arrays, and storing your data is no different. The use of objects instead of models makes persistence an extremely flexible proposition: you can write what you want when you want and, unlike with object-relational models, ignore anything you don't know yet as long as it's not required by table constraints. The persistence methods operate only on the columns you specify, and leave all others with the existing or default value, as appropriate.

The documentation for persistence is written with tables in mind, but you can also persist data through views which meet certain criteria using the same methods.

save

save persists a single object to the database. On initialization, Massive records your tables' primary key information and uses this to determine whether the object passed to save represents a new or an existing row and invokes insert or update appropriately. This means it is not a true upsert, and that some care must be taken with its use in certain situations! The promise save returns will resolve to the created or modified record represented as an object.

Restrictions

  • save may not be used with foreign tables or updatable views, since they cannot have primary keys. If you need to persist data to a foreign table or updatable view, use insert and update.
  • save should generally be avoided if your table's primary key does not include at least one column sourced from a sequence or function (eg an auto-incrementing integer or one of the UUID-generating functions). If you are trying to create a record and your table definition requires you to specify values for all primary key columns, you must use insert; save is effectively an update in this scenario.

Options

Query options valid for insert, and for results processing, may be used with save as a second argument. However, most of these are of limited utility.

db.tests.save({
  version: 1,
  name: 'homepage'
}).then(tests => {
  // the newly-inserted test
});

db.tests.save({
  id: 1,
  version: 2,
  priority: 'high'
}).then(tests => {
  // the updated test, still with the name 'homepage'
});

insert

insert writes a new object into your table. The object's keys should match your column names exactly. You do not have enumerate every single field in your object; as long as you ensure that no NOT NULL constraints will be violated, you can include as many or as few fields as you need. When you insert an object, the record will be returned as an object. If you have an autogenerated or serial primary key or default fields, they will have been calculated and added.

db.tests.insert({
  name: 'homepage',
  version: 1,
}).then(test => {
  // the newly-inserted test
});

Multiple Records

You can insert multiple records at once -- just pass an array, and you'll receive an array containing all your new data:

db.tests.insert([{
  name: 'homepage',
  version: 1,
  priority: 'low'
}, {
  name: 'about us',
  version: 1
}]).then(tests => {
  // an array containing both newly-inserted tests
});

Fields may be omitted if the database default value is intended; however, fields having NOT NULL constraints must be included or the insert will fail.

Deep Insert

Inserting into multiple related tables at once happens fairly frequently, especially when you're dealing with many-to-many relationships through a junction table. For these cases, you can pass arrays of related records into insert as a property named for the related table.

Entries in each related table array have the same conditions as those for normal inserts: they must be consistently formed, and nullable fields may be omitted if no value is intended. The one difference is that the foreign key to the original record must be explicitly undefined.

db.tests.insert({
  name: 'homepage',
  version: 1,
  priority: 'low',
  user_tests: [{
    test_id: undefined,
    user_id: 1,
    role: 'primary'
  }, {
    test_id: undefined,
    user_id: 2,
    role: 'auxiliary'
  }]
}, {
  deepInsert: true
}).then(tests => {
  // as with regular inserts, you only get the test back;
  // if user_tests has a primary key constraint, you can
  // query db.user_tests to retrieve its rows.
});

Deep insert is only supported when inserting single records. Attempting to deep insert an array of records will raise an exception.

Options

Query options for insert and results processing may be used:

db.tests.insert({
  id: 1,
  name: 'homepage',
  version: 1
}, {
  onConflictIgnore: true, // if the id exists, do nothing
}).then(test => {
  // the inserted row, or `null` if there was a conflict
  // and nothing was inserted
});

update

update alters existing records in a table, given a criteria object (or unary primary key) and a map of column names to the new values. It returns a promise for an array containing the updated record(s) if passed a criteria object, or a promise for an object if passed a primary key.

db.tests.update({
  priority: 'high'
}, {
  priority: 'moderate'
}).then(tests => {
  // an array containing all tests which formerly had
  // priority 'high'. Since this issues a prepared
  // statement, note that the version field cannot
  // be incremented here!
});

update can use the only query option, as well as options affecting results processing:

db.tests.update({
  priority: 'high'
}, {
  priority: 'moderate'
}, {build: true}).then(test => {
  // builds the query without executing it
});

destroy

destroy removes data either by primary key or by matching a criteria object. In the former case, it returns a promise for the deleted record object; in the latter, it returns a promise for an array containing all deleted records, even if no or one records were deleted.

destroy may use the only query option, as well as options affecting results processing.

db.tests.destroy(1).then(test => {
  // test #1
});

db.tests.destroy({
  priority: 'high'
}, {only: true}).then(tests => {
  // an array containing all removed tests; the 'only'
  // flag prevents the query from affecting descendant
  // tables
});