Persistence

The query API retrieves your data in the form of ordinary 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, without having to construct or query an entity instance (this is equally cause to be careful; woe betide the author of a destroy who misses execution paths which produce incomplete criteria). The persistence methods operate only on the columns you specify, and leave all others with the existing or default value, as appropriate.

Persistence methods are ordinarily the province of tables, but you can also write data through "updatable" views which meet certain criteria.

save

save stores a single object in the database. Massive determines whether or not the object contains primary key information, updating it if it does or inserting it otherwise. This means it is not a true upsert, and that some care must be taken with its use in certain situations! save yields the created or modified record represented as an object.

Some restrictions apply:

  • 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 is only useful with tables having a primary key wholly or partially generated in the database, such as an auto-incrementing sequence or one of the UUID-creating 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.

Insert options, as well as the universal options, may be passed as a second argument. However, most of these are of limited utility.

let test = await db.tests.save({
  version: 1,
  name: 'homepage'
});

test.version = 2;
test.priority = 'high';

test = await db.tests.save(test);

insert

insert writes a new record or records into your table. You do not have to include every single field of the target table; 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.

const test = await db.tests.insert({
  name: 'homepage',
  version: 1,
});

To insert multiple records, pass an array of records instead:

const tests = await db.tests.insert([{
  name: 'homepage',
  version: 1,
  priority: 'low'
}, {
  name: 'about us',
  version: 1
}]);

Insert options, as well as the universal options, may be passed as a second argument.

const testOrNull = await db.tests.insert({
  id: 1,
  name: 'homepage',
  version: 1
}, {
  onConflictIgnore: true, // if the id exists, do nothing
});

insert yields an object if an object record was passed, or an array for an array of records.

Deep Insert

This is automatically enabled with joins in Massive v6 and up.

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. Related records must have the foreign key to the original record explicitly set to undefined.

const onlyTheTest = await 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
});

Deep insert is only supported when inserting single records. Attempts to deep insert an array of records will be rejected.

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 yields an array containing the zero or more updated record(s) if passed a criteria object, or an object if passed a primary key.

The second "changes" argument can include a $set key with a map of column names to raw SQL expressions. This allows arithmetic, column references, function calls, and other such dynamic changes to be made. However, raw SQL can introduce SQL injection vulnerabilities if used carelessly. Don't interpolate user input into $set expressions!

const tests = await db.tests.update(1, {
  priority: 'moderate',
  $set: {
    self_destruct_in: 'self_destruct_in - 1'
  }
});

update can use the universal query options:

const sqlAndParams = await db.tests.update({
  priority: 'high'
}, {
  priority: 'moderate'
}, {
  build: true
});

destroy

destroy removes records either by primary key or by matching a criteria object. It yields an array containing the zero or more deleted record(s) if passed a criteria object, or an object if passed a primary key.

destroy can use the universal query options:

const deletedTest = await db.tests.destroy(1);

const deletedTests = await db.tests.destroy({
  priority: 'high'
}, {
  only: true
});