You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

86 lines
3.5 KiB
JavaScript

"use strict";
const lookupTimezoneName = require("../lookup-timezone-name");
// CAUTION: Once a type is defined here and that version of the code is released, no new requiredAttributes can be added to that type! Otherwise it would be possible for an old migration (from before that change) to become retroactively invalid upon an update of zapdb, just because it was written against an older ruleset. This cannot be fixed by statefully tracking what migrations were *once* considered 'valid', either; all state is kept in the database instance of a specific deployment, which means that it would only be considered valid for deployments created prior to the zapdb upgrade - making database deployment non-deterministic, as a given migration would be considered valid on one system but invalid on another. Bottom line: we can never retroactively invalidate existing migrations.
// TODO: Consider using schema API versions as a workaround for this, and changing the rules format to specify since which version an attribute is required, providing a default for the alternate case. Question is whether by this point it's really useful to even specify new required fields *at all*, or whether we can get away with just specifying defaults.
module.exports = {
// Note that this mapping can be used to determine the losslessness of both forward and backward migrations!
types: {
bytes: {
requiredAttributes: new Set([ "required" ]),
losslessConversionTo: {}
},
string: {
requiredAttributes: new Set([ "required" ]),
losslessConversionTo: {
bytes: (string) => Buffer.from(string, "utf8")
}
},
decimal: {
requiredAttributes: new Set([ "required", "signed", "precision" ]),
losslessConversionTo: {
// Decimals are already internally represented as strings
string: (decimal) => decimal
}
},
integer: {
requiredAttributes: new Set([ "required", "signed" ]),
losslessConversionTo: {
string: (integer) => String(integer),
decimal: (integer) => String(integer)
}
},
boolean: {
requiredAttributes: new Set([ "required" ]),
losslessConversionTo: {}
},
date: {
requiredAttributes: new Set([ "required", "withTimezone" ]),
losslessConversionTo: {}
},
duration: {
requiredAttributes: new Set([ "required" ]),
losslessConversionTo: {}
},
},
attributes: {
precision: {
validForTypes: new Set([ "decimal" ]),
isLossless: (oldSetting, newSetting) => (newSetting > oldSetting),
losslessTransformer: (value) => value, // No change to value
requiresMigrationDefault: false // Whether setting this option causes a 'migration default' to become required to specify? Need to figure out what this was supposed to be for
},
signed: {
validForTypes: new Set([ "decimal", "integer" ]),
isLossless: (oldSetting, newSetting) => (oldSetting === false && newSetting === true),
losslessTransformer: (value, _oldAttribute, _newAttribute) => value, // No change
requiresMigrationDefault: false,
},
withTimezone: {
validForTypes: new Set([ "date" ]),
isLossless: (oldSetting, newSetting) => (oldSetting === false && newSetting === true),
losslessTransformer: (value) => ({
... value,
timezone: lookupTimezoneName("Etc/UTC")
}),
requiresMigrationDefault: false
},
required: {
// Valid for all types
validForTypes: true,
isLossless: () => true,
requiresMigrationDefault: true
},
defaultValue: { // For newly inserted rows, not necessarily for migrated rows! See notes.txt
validForTypes: true,
isLossless: () => true,
requiresMigrationDefault: false
}
},
operations: {
//
}
};