Closed bimusiek closed 7 years ago
How does your schema look like? The fact that you are using insertOrReplace()
does not guarantee that there will be no collisions of PK. You can still be accidentally attempting to insert the same PK twice, since you can supply your own primary keys.
Are you using the "auto-increment primary key" feature? Could you post some snippets of the code that inserts to the DB?
Also a couple unrelated suggestions.
storeType
, Lovefield will automatically fall back to WEB_SQL on Safari, and use INDEXED_DB otherwise, see fallback logic at https://github.com/google/lovefield/blob/master/lib/base.js#L58-L64.connectionObservers
variable, or for the isConnecting
boolean.Instead you could simply store a single Promise, and return that promise from future calls to connect().
connect () {
if (this.whenConnected) {
return this.whenConnected;
}
this.whenConnected = db.ahoydb.connect({
onUpgrade: this.onUpgrade.bind(this),
storeType: this.lf.schema.DataStoreType.WEB_SQL
}).then((db) => {
this.connection = db;
return this;
});
return this.whenConnected;
}
Actually let me rephrase one of my comments above.
The fact that you are using insertOrReplace()
means that if you insert a row with the same PK, it should replace an existing row instead of complaining. So, yes, that would be a bug if it is not happening. Again, please post an example of the schema and the code that inserts to the DB, to see if anything stands out.
I am using SPAC to generate schema files, so my schema (yml) looks like this:
%YAML 1.2
---
name: ahoydb
version: 7
table:
FlightBookingGrouper:
column:
id: integer
constraint:
primaryKey: [ id ]
FlightBooking:
column:
id: integer
reference: string
currency: string
modifiedAt: datetime
createdAt: datetime
status: string
grouperId: integer
airlineId: integer
amount: string
constraint:
primaryKey: [ id ]
foreignKey:
fkGrouperId:
local: grouperId
ref: FlightBookingGrouper.id
fkAirlineId:
local: airlineId
ref: Airline.id
FlightBookingLeg:
column:
id: integer
flightBookingId: integer
firstSectionTakeoffDateTZ: datetime
lastSectionLandingDateTZ: datetime
constraint:
primaryKey: [ id ]
foreignKey:
fkFlightBookingId:
local: flightBookingId
ref: FlightBooking.id
action: cascade
FlightBookingSection:
column:
id: integer
legId: integer
flightBookingId: integer
flightNumber: string
seat: string
gate: string
terminal: string
fareType: string
baggageAllowance: string
flightDuration: number
takeoffDate: datetime
landingDate: datetime
takeoffDateTZ: datetime
landingDateTZ: datetime
boardingDate: datetime
ticketCancellationFee: boolean
ticketCancellationFeeCurrency: string
ticketCancellationFeeAmount: string
ticketChangeFee: boolean
ticketChangeFeeCurrency: string
ticketChangeFeeAmount: string
status: string
airlineId: integer
arrivalAirportId: integer
departureAirportId: integer
constraint:
nullable: [seat, gate, terminal, fareType, flightNumber, baggageAllowance, ticketCancellationFeeCurrency, ticketCancellationFeeAmount, ticketChangeFeeCurrency, ticketChangeFeeAmount, boardingDate]
primaryKey: [ id ]
fkLegId:
local: legId
ref: FlightBookingLeg.id
fkFlightBookingId:
local: flightBookingId
ref: FlightBooking.id
action: cascade
fkAirlineId:
local: airlineId
ref: Airline.id
fkArrivalAirportId:
local: arrivalAirportId
ref: Airport.id
fkDepartureAirportId:
local: departureAirportId
ref: Airport.id
Airline:
column:
id: integer
name: string
constraint:
primaryKey: [ id ]
Airport:
column:
id: integer
name: string
city: string
iata: string
tz: string
dst: string
latitude: string
longitude: string
constraint:
primaryKey: [ id ]
Passenger:
column:
id: integer
ticketNumber: string
flightBookingId: integer
ticketPrice: string
taxAmount: string
customerId: integer
constraint:
primaryKey: [ id ]
nullable: [ticketNumber, ticketPrice, taxAmount]
fkFlightBookingId:
local: flightBookingId
ref: FlightBooking.id
action: cascade
fkCustomerId:
local: customerId
ref: Customer.id
Customer:
column:
id: integer
title: string
firstName: string
middleName: string
lastName: string
gender: string
smoochId: string
nationality: string
company: string
notes: string
travelPreferences: string
birthday: datetime
constraint:
primaryKey: [id]
nullable: [title, firstName, middleName, lastName, gender, smoochId, nationality, company, notes, travelPreferences, birthday]
Charge:
column:
id: integer
amount: string
type: string
name: string
flightBookingId: integer
constraint:
primaryKey: [id]
nullable: [name]
fkFlightBookingId:
local: flightBookingId
ref: FlightBooking.id
action: cascade
ChargeRequest:
column:
id: integer
expireAt: datetime
flightBookingId: integer
type: string
amount: string
currency: string
constraint:
primaryKey: [id]
nullable: [type]
fkFlightBookingId:
local: flightBookingId
ref: FlightBooking.id
action: cascade
ChargeRequestAck:
column:
id: integer
customerId: integer
statusChangedAt: datetime
status: string
creditCardId: integer
forwardToUserId: integer
chargeRequestId: integer
constraint:
primaryKey: [id]
nullable: [creditCardId, forwardToUserId, statusChangedAt]
fkForwardToUserId:
local: forwardToUserId
ref: User.id
fkCreditCardId:
local: creditCardId
ref: Card.id
fkChargeRequestId:
local: chargeRequestId
ref: ChargeRequest.id
action: cascade
Card:
column:
id: integer
name: string
mask: string
brand: string
addressName: string
addressLine1: string
addressLine2: string
addressCity: string
addressZip: string
addressState: string
addressCountry: string
constraint:
primaryKey: [id]
nullable: [addressName, addressLine1, addressLine2, addressCity, addressZip, addressState, addressCountry]
User:
column:
id: integer
username: string
customerId: integer
constraint:
primaryKey: [id]
fkCustomerId:
local: customerId
ref: Customer.id
Then this is how I create Leg model for example
static map (db, legId, sections, bookingId) {
const FlightBookingLeg = db.connection.getSchema().getFlightBookingLeg();
const row = FlightBookingLeg.createRow({
id: parseInt(legId),
flightBookingId: bookingId,
firstSectionTakeoffDateTZ: sections[ 0 ].takeoffDate.toDate(),
lastSectionLandingDateTZ: sections[ sections.length - 1 ].landingDate.toDate()
});
return db.connection.insertOrReplace().into(FlightBookingLeg).values([ row ]).exec().then(
(rows) => {
return rows[ 0 ];
})
.then((leg) => {
const promises = sections.map((section) => {
return FlightSectionMapper.map(db, section, legId, bookingId);
});
return Promise.all(promises).then(() => {
return leg;
});
});
}
About your unrelated suggestions:
Also, I am genereting lovefield sources quite unusual.
Because SPAC generates file with adding Google Closure stuff to the code, I had to checkout latest lovefield and google closure library and I am using compiler.jar
to generate one file with my schema + lovefield.
Code generation looks like this:
${SCRIPTPATH}/spac.sh --schema ${SCRIPTPATH}/../app/libs/database/schema.yml --outputdir ${SCRIPTPATH}/../app/libs/database/lovefield/ --namespace ahoydb
java -jar ${SCRIPTPATH}/../tools/lovefield/node_modules/google-closure-compiler/compiler.jar --dependency_mode=STRICT --entry_point='ahoydb.row.FlightBooking' --js_output_file=${SCRIPTPATH}/../app/libs/database/ahoydb.js "${SCRIPTPATH}/../tools/lovefield/node_modules/google-closure-library/**.js" '!**_test.js' "${SCRIPTPATH}/../tools/lovefield/lib/**.js" "${SCRIPTPATH}/../app/libs/database/lovefield/**.js"
I couldn't make it work otherwise with Webpack, this way I get one file with ahoydb
and lf
variables that webpack can export from the file and I can access them outside.
I tried to investigate more, however, I couldn't find why this happens. Exception is thrown already after Row was inserted so I have no idea even when duplicated Row is inserted.
I am using all around the code only insertOrReplace
, no updates, no inserts etc.
My workaround, for now, is to in case of error, change DB name (save it to local storage) and then I am overriding ahoydb.schema.Database.prototype.name
. This way database is recreated.
Thanks for the snippets. I could not spot anything suspicious in the schema. Also the way you are building Lovefield, given that you are using SPAC seems fine too.
It is hard to remote debug the issue. A minimal repro case would be helpful, but I guess you don't have an easy minimal repro yet. I suggest the following:
checkForeignKeysForUpdate
or checkForeignKeysForUpdate
).From the symptoms you are describing it seems that Lovefield is mis-classifying a "replace" for an "insert" and therefore it performs the wrong constraint checks resulting in the error above.
From the screenshot you posted at the 1st post, the Database is already in a corrupt state (duplicate PK has been persisted), so from that point on, every time you reload the page, an error will be thrown. The question is to figure out how was the DB corrupted and fix that.
Other things to try: Can you reproduce this with INDEXED_DB? Perhaps it could be a bug affecting WEB_SQL only.
Regarding unrelated suggestion 1 in previous post: Yes this is a known issue 168. I am suggesting to do your own IOS detection until it is fixed, instead of using WEB_SQL everywhere, such that you always specify the "storeType" in connectOptions.
I did not have time to debug with unminified code, however I changed detection so WebSQL is used only on Safari and all looks fine so far. However my hack could also help where in case of database problem I am dynamicly changing the name (and persisting in local storage) so db will be recreated.
I did not find easy way to re-create database so I had to do this trick with making new one with different name.
I have the same issue with IndexedDB storage. I open my application in 2 tabs in Chrome browser. If a tab deletes a record in IndexedDB but the remain tab still adds this record with same id to IndexedDB, we will have 2 record in IndexedDB with the same primary key. I close the browser and open again, the 201 error will say 'hi' to me.
@nccnm Awesome that you found why it fails! I only saw results of the bug.
If you are interested here is how I fixed it, I know it is not pretty or perfect but it works 👍
import SynchronizerDispatcher from "../../synchronizer/dispatcher";
import Console from "../console";
const db = require("imports?this=>window!exports?ahoydb&lf!./ahoydb");
const DATABASE_NAME_LS = "lovefield.database.name";
db.ahoydb.schema.Database.prototype.name = () => {
let lsDbName = localStorage.getItem(DATABASE_NAME_LS);
if(!lsDbName) {
lsDbName = "ahoydb_web";
localStorage.setItem(DATABASE_NAME_LS, lsDbName);
}
return lsDbName;
};
class Database {
constructor () {
this.ahoydb = db.ahoydb;
this.lf = db.lf;
this.connection = null;
this.whenConnected = null;
}
connect () {
if (this.whenConnected) {
return this.whenConnected;
}
const self = this;
var props = {
onUpgrade: this.onUpgrade.bind(this)
};
if (navigator.userAgent.indexOf("Safari") != -1 && navigator.userAgent.indexOf("Chrome") == -1) {
props.storeType = this.lf.schema.DataStoreType.WEB_SQL;
}
this.whenConnected = db.ahoydb.connect(props).then((db) => {
self.connection = db;
return this;
}, error => {
if(error.code == 201 || error.code == 5) {
return self.resolveDbError();
}
});
return this.whenConnected;
}
onUpgrade (rawDb) {
const version = rawDb.getVersion();
Console.log("Upgrading ver. "+version);
return Promise.resolve().then(() => {
if(version < 2) {
return rawDb.addTableColumn("ChargeRequest", "extensionCounter", 0);
}
}).then(() => {
if(version < 3) {
return rawDb.addTableColumn("FlightBookingGrouper", "lastFlightBookingId", 0);
}
}).then(() => {
// have it as the last element of the promise chain
return rawDb.dump();
});
}
resolveDbError() {
// One way to fix database is to rename it so it will be re-created
localStorage.setItem(DATABASE_NAME_LS, localStorage.getItem(DATABASE_NAME_LS)+"_");
this.whenConnected = null;
SynchronizerDispatcher.clear();
return this.connect();
}
}
export default new Database();
Unfortunately I did not find the easy way to delete and recreate the database, because when you get error LF is already connected and not all the browsers offer the option to really remove the db. Another temporary solution would be to clean the fields that are failing however again you cannot do it with LF as it just failed and you would have to connect directly to the database by yourself, which makes the whole LF obsolete then for me...
/cc @arthurhsu Multiple tabs are not supported, per https://github.com/google/lovefield/blob/master/docs/spec/99_postfix.md#multi-connection. There is currently no (easy) way to sync the state (in-memory indices, in-memory cache) across the two tabs, therefore the DB gets corrupted (constraint violations make it to the persisted data) when used that way.
@bimusiek Hi, Could you explain a bit how you generated Schema file from YML file. Thanks.
@veenedu Script to install lovefield localy:
#!/usr/bin/env bash
# Install Lovefield SPAC
OLD_PWD=$PWD
mkdir tools | true
cd tools
rm -rf lovefield | true
git clone https://github.com/google/lovefield.git
cd lovefield
git checkout v2.1.11
npm install
cd $OLD_PWD
and then I have spac.sh
helper
#!/usr/bin/env bash
SCRIPTPATH=$(dirname $BASH_SOURCE)
exec node $SCRIPTPATH/lovefield/spac/spac.js $@
and finally script to generate lovefield with schema
#!/usr/bin/env bash
SCRIPTPATH=$(dirname $BASH_SOURCE)
${SCRIPTPATH}/spac.sh --schema ${SCRIPTPATH}/../web/app/libs/database/schema.yml --outputdir ${SCRIPTPATH}/../web/app/libs/database/lovefield/ --namespace ahoydb
java -jar ${SCRIPTPATH}/../tools/lovefield/node_modules/google-closure-compiler/compiler.jar --dependency_mode=STRICT --entry_point='ahoydb.row.FlightBooking' --js_output_file=${SCRIPTPATH}/../web/app/libs/database/ahoydb.js "${SCRIPTPATH}/../tools/lovefield/node_modules/google-closure-library/**.js" '!**_test.js' "${SCRIPTPATH}/../tools/lovefield/lib/**.js" "${SCRIPTPATH}/../web/app/libs/database/lovefield/**.js"
You have to tweak it for your purposes, so you have to change entry_point
and js_output_file
and thats should be all. :)
@bimusiek
for below it generates YAML it generates Javascript file, as specified far below. That file contains lots of goog.provide
at the beginning. Is that expected behaviour? Do you think I have generated correct schema file? If yes how to resolve those goog
%YAML 1.2
---
name: crdb
version: 1
table:
ImageCache:
column:
remote: string
local: string
constraint:
primaryKey: [ remote ]
Asset:
column:
id: string
asset: string
timestamp: integer
constraint:
primaryKey: [ id ]
Pin:
column:
id: string
state: integer
sessionId: string
constraint:
foreignKey:
fkId:
local: id
ref: Asset.id
action: cascade
InfoCard:
column:
id: string
lang: string
itag: integer
country: string
fileName: string
constraint:
primaryKey: [ id, lang ]
unique:
uniqFN:
column: [ fileName ]
index:
idxPinItag:
column: [ itag ]
goog.provide('veen.row.Asset');
goog.provide('veen.row.AssetDbType');
goog.provide('veen.row.AssetType');
goog.provide('veen.row.ImageCache');
goog.provide('veen.row.ImageCacheDbType');
goog.provide('veen.row.ImageCacheType');
goog.provide('veen.row.InfoCard');
goog.provide('veen.row.InfoCardDbType');
goog.provide('veen.row.InfoCardType');
goog.provide('veen.row.Pin');
goog.provide('veen.row.PinDbType');
goog.provide('veen.row.PinType');
goog.provide('veen.schema.Asset');
goog.provide('veen.schema.Database');
goog.provide('veen.schema.ImageCache');
goog.provide('veen.schema.InfoCard');
goog.provide('veen.schema.Pin');
/** @suppress {extraRequire} */
goog.require('lf.ConstraintAction');
goog.require('lf.ConstraintTiming');
goog.require('lf.Order');
goog.require('lf.Row');
goog.require('lf.Type');
goog.require('lf.schema.BaseColumn');
goog.require('lf.schema.Constraint');
goog.require('lf.schema.Database');
goog.require('lf.schema.ForeignKeySpec');
goog.require('lf.schema.Index');
goog.require('lf.schema.Info');
goog.require('lf.schema.Table');
/**
* @implements {lf.schema.Database}
* @constructor
*/
veen.schema.Database = function() {
/** @private {!Object} */
this.tableMap_ = {};
/** @private {!lf.schema.Database.Pragma} */
this.pragma_ = {
enableBundledMode: false
};
/** @private {!veen.schema.ImageCache} */
this.imageCache_ = new veen.schema.ImageCache();
this.tableMap_['ImageCache'] = this.imageCache_;
/** @private {!veen.schema.Asset} */
this.asset_ = new veen.schema.Asset();
this.tableMap_['Asset'] = this.asset_;
/** @private {!veen.schema.Pin} */
this.pin_ = new veen.schema.Pin();
this.tableMap_['Pin'] = this.pin_;
/** @private {!veen.schema.InfoCard} */
this.infoCard_ = new veen.schema.InfoCard();
this.tableMap_['InfoCard'] = this.infoCard_;
/** @private {!lf.schema.Info} */
this.metaInfo_;
};
/** @override */
veen.schema.Database.prototype.name = function() {
return 'crdb';
};
/** @override */
veen.schema.Database.prototype.version = function() {
return 1;
};
/** @override */
veen.schema.Database.prototype.tables = function() {
return [
this.imageCache_,
this.asset_,
this.pin_,
this.infoCard_
];
};
/** @override */
veen.schema.Database.prototype.info = function() {
if (!this.metaInfo_) {
this.metaInfo_ = new lf.schema.Info(this);
}
return this.metaInfo_;
};
/** @override */
veen.schema.Database.prototype.table = function(tableName) {
return this.tableMap_[tableName] || null;
};
/** @override */
veen.schema.Database.prototype.pragma = function() {
return this.pragma_;
};
/** @return {!veen.schema.ImageCache} */
veen.schema.Database.prototype.getImageCache = function() {
return this.imageCache_;
};
/** @return {!veen.schema.Asset} */
veen.schema.Database.prototype.getAsset = function() {
return this.asset_;
};
/** @return {!veen.schema.Pin} */
veen.schema.Database.prototype.getPin = function() {
return this.pin_;
};
/** @return {!veen.schema.InfoCard} */
veen.schema.Database.prototype.getInfoCard = function() {
return this.infoCard_;
};
/**
* @extends {lf.schema.Table.<!veen.row.ImageCacheType,
* !veen.row.ImageCacheDbType>}
* @constructor
*/
veen.schema.ImageCache = function() {
var cols = [];
/** @type {!lf.schema.BaseColumn.<string>} */
this.remote = new lf.schema.BaseColumn(
this, 'remote', true, false, lf.Type.STRING);
cols.push(this.remote);
/** @type {!lf.schema.BaseColumn.<string>} */
this.local = new lf.schema.BaseColumn(
this, 'local', false, false, lf.Type.STRING);
cols.push(this.local);
var indices = [
new lf.schema.Index('ImageCache', 'pkImageCache', true,
[
{schema: this.remote, order: lf.Order.ASC, autoIncrement: false}
])
];
/** @private {!lf.schema.Constraint} */
this.constraint_;
veen.schema.ImageCache.base(
this, 'constructor', 'ImageCache', cols, indices, false);
};
goog.inherits(veen.schema.ImageCache, lf.schema.Table);
/** @override */
veen.schema.ImageCache.prototype.createRow = function(opt_value) {
return new veen.row.ImageCache(lf.Row.getNextId(), opt_value);
};
/** @override */
veen.schema.ImageCache.prototype.deserializeRow =
function(dbRecord) {
var data = dbRecord['value'];
return new veen.row.ImageCache(dbRecord['id'], data);
};
/** @override */
veen.schema.ImageCache.prototype.getConstraint = function() {
if (goog.isDefAndNotNull(this.constraint_)) {
return this.constraint_;
}
var pk = this.getIndices()[0];
var notNullable = [
this.remote,
this.local
];
var foreignKeys = [
];
this.constraint_ = new lf.schema.Constraint(
pk, notNullable, foreignKeys);
return this.constraint_;
};
/**
* @export
* @constructor
* @struct
* @final
*/
veen.row.ImageCacheType = function() {
/** @export @type {string} */
this.remote;
/** @export @type {string} */
this.local;
};
/**
* @export
* @constructor
* @struct
* @final
*/
veen.row.ImageCacheDbType = function() {
/** @export @type {string} */
this.remote;
/** @export @type {string} */
this.local;
};
/**
* Constructs a new ImageCache row.
* @constructor
* @extends {lf.Row.<!veen.row.ImageCacheType,
* !veen.row.ImageCacheDbType>}
*
* @param {number} rowId The row ID.
* @param {!veen.row.ImageCacheType=} opt_payload
*/
veen.row.ImageCache = function(rowId, opt_payload) {
veen.row.ImageCache.base(this, 'constructor', rowId, opt_payload);
};
goog.inherits(veen.row.ImageCache, lf.Row);
/** @override */
veen.row.ImageCache.prototype.defaultPayload = function() {
var payload = new veen.row.ImageCacheType();
payload.remote = '';
payload.local = '';
return payload;
};
/** @override */
veen.row.ImageCache.prototype.toDbPayload = function() {
var payload = new veen.row.ImageCacheDbType();
payload.remote = this.payload().remote;
payload.local = this.payload().local;
return payload;
};
/** @override */
veen.row.ImageCache.prototype.keyOfIndex = function(indexName) {
switch (indexName) {
case 'ImageCache.pkImageCache':
return this.payload().remote;
case 'ImageCache.#':
return this.id();
default:
break;
}
return null;
};
/** @return {string} */
veen.row.ImageCache.prototype.getRemote = function() {
return this.payload().remote;
};
/**
* @param {string} value
* @return {!veen.row.ImageCache}
*/
veen.row.ImageCache.prototype.setRemote = function(value) {
this.payload().remote = value;
return this;
};
/** @return {string} */
veen.row.ImageCache.prototype.getLocal = function() {
return this.payload().local;
};
/**
* @param {string} value
* @return {!veen.row.ImageCache}
*/
veen.row.ImageCache.prototype.setLocal = function(value) {
this.payload().local = value;
return this;
};
/**
* @extends {lf.schema.Table.<!veen.row.AssetType,
* !veen.row.AssetDbType>}
* @constructor
*/
veen.schema.Asset = function() {
var cols = [];
/** @type {!lf.schema.BaseColumn.<string>} */
this.id = new lf.schema.BaseColumn(
this, 'id', true, false, lf.Type.STRING);
cols.push(this.id);
/** @type {!lf.schema.BaseColumn.<string>} */
this.asset = new lf.schema.BaseColumn(
this, 'asset', false, false, lf.Type.STRING);
cols.push(this.asset);
/** @type {!lf.schema.BaseColumn.<number>} */
this.timestamp = new lf.schema.BaseColumn(
this, 'timestamp', false, false, lf.Type.INTEGER);
cols.push(this.timestamp);
var indices = [
new lf.schema.Index('Asset', 'pkAsset', true,
[
{schema: this.id, order: lf.Order.ASC, autoIncrement: false}
])
];
/** @private {!lf.schema.Constraint} */
this.constraint_;
veen.schema.Asset.base(
this, 'constructor', 'Asset', cols, indices, false);
};
goog.inherits(veen.schema.Asset, lf.schema.Table);
/** @override */
veen.schema.Asset.prototype.createRow = function(opt_value) {
return new veen.row.Asset(lf.Row.getNextId(), opt_value);
};
/** @override */
veen.schema.Asset.prototype.deserializeRow =
function(dbRecord) {
var data = dbRecord['value'];
return new veen.row.Asset(dbRecord['id'], data);
};
/** @override */
veen.schema.Asset.prototype.getConstraint = function() {
if (goog.isDefAndNotNull(this.constraint_)) {
return this.constraint_;
}
var pk = this.getIndices()[0];
var notNullable = [
this.id,
this.asset,
this.timestamp
];
var foreignKeys = [
];
this.constraint_ = new lf.schema.Constraint(
pk, notNullable, foreignKeys);
return this.constraint_;
};
/**
* @export
* @constructor
* @struct
* @final
*/
veen.row.AssetType = function() {
/** @export @type {string} */
this.id;
/** @export @type {string} */
this.asset;
/** @export @type {number} */
this.timestamp;
};
/**
* @export
* @constructor
* @struct
* @final
*/
veen.row.AssetDbType = function() {
/** @export @type {string} */
this.id;
/** @export @type {string} */
this.asset;
/** @export @type {number} */
this.timestamp;
};
/**
* Constructs a new Asset row.
* @constructor
* @extends {lf.Row.<!veen.row.AssetType,
* !veen.row.AssetDbType>}
*
* @param {number} rowId The row ID.
* @param {!veen.row.AssetType=} opt_payload
*/
veen.row.Asset = function(rowId, opt_payload) {
veen.row.Asset.base(this, 'constructor', rowId, opt_payload);
};
goog.inherits(veen.row.Asset, lf.Row);
/** @override */
veen.row.Asset.prototype.defaultPayload = function() {
var payload = new veen.row.AssetType();
payload.id = '';
payload.asset = '';
payload.timestamp = 0;
return payload;
};
/** @override */
veen.row.Asset.prototype.toDbPayload = function() {
var payload = new veen.row.AssetDbType();
payload.id = this.payload().id;
payload.asset = this.payload().asset;
payload.timestamp = this.payload().timestamp;
return payload;
};
/** @override */
veen.row.Asset.prototype.keyOfIndex = function(indexName) {
switch (indexName) {
case 'Asset.pkAsset':
return this.payload().id;
case 'Asset.#':
return this.id();
default:
break;
}
return null;
};
/** @return {string} */
veen.row.Asset.prototype.getId = function() {
return this.payload().id;
};
/**
* @param {string} value
* @return {!veen.row.Asset}
*/
veen.row.Asset.prototype.setId = function(value) {
this.payload().id = value;
return this;
};
/** @return {string} */
veen.row.Asset.prototype.getAsset = function() {
return this.payload().asset;
};
/**
* @param {string} value
* @return {!veen.row.Asset}
*/
veen.row.Asset.prototype.setAsset = function(value) {
this.payload().asset = value;
return this;
};
/** @return {number} */
veen.row.Asset.prototype.getTimestamp = function() {
return this.payload().timestamp;
};
/**
* @param {number} value
* @return {!veen.row.Asset}
*/
veen.row.Asset.prototype.setTimestamp = function(value) {
this.payload().timestamp = value;
return this;
};
/**
* @extends {lf.schema.Table.<!veen.row.PinType,
* !veen.row.PinDbType>}
* @constructor
*/
veen.schema.Pin = function() {
var cols = [];
/** @type {!lf.schema.BaseColumn.<string>} */
this.id = new lf.schema.BaseColumn(
this, 'id', false, false, lf.Type.STRING);
cols.push(this.id);
/** @type {!lf.schema.BaseColumn.<number>} */
this.state = new lf.schema.BaseColumn(
this, 'state', false, false, lf.Type.INTEGER);
cols.push(this.state);
/** @type {!lf.schema.BaseColumn.<string>} */
this.sessionId = new lf.schema.BaseColumn(
this, 'sessionId', false, false, lf.Type.STRING);
cols.push(this.sessionId);
var indices = [
new lf.schema.Index('Pin', 'fkId', false,
[
{schema: this.id, order: lf.Order.ASC}
])
];
/** @private {!lf.schema.Constraint} */
this.constraint_;
veen.schema.Pin.base(
this, 'constructor', 'Pin', cols, indices, false);
};
goog.inherits(veen.schema.Pin, lf.schema.Table);
/** @override */
veen.schema.Pin.prototype.createRow = function(opt_value) {
return new veen.row.Pin(lf.Row.getNextId(), opt_value);
};
/** @override */
veen.schema.Pin.prototype.deserializeRow =
function(dbRecord) {
var data = dbRecord['value'];
return new veen.row.Pin(dbRecord['id'], data);
};
/** @override */
veen.schema.Pin.prototype.getConstraint = function() {
if (goog.isDefAndNotNull(this.constraint_)) {
return this.constraint_;
}
var pk = null;
var notNullable = [
this.id,
this.state,
this.sessionId
];
var foreignKeys = [
new lf.schema.ForeignKeySpec(
{
'local': 'id',
'ref': 'Asset.id',
'action': lf.ConstraintAction.CASCADE,
'timing': lf.ConstraintTiming.IMMEDIATE
}, 'Pin', 'fkId')
];
this.constraint_ = new lf.schema.Constraint(
pk, notNullable, foreignKeys);
return this.constraint_;
};
/**
* @export
* @constructor
* @struct
* @final
*/
veen.row.PinType = function() {
/** @export @type {string} */
this.id;
/** @export @type {number} */
this.state;
/** @export @type {string} */
this.sessionId;
};
/**
* @export
* @constructor
* @struct
* @final
*/
veen.row.PinDbType = function() {
/** @export @type {string} */
this.id;
/** @export @type {number} */
this.state;
/** @export @type {string} */
this.sessionId;
};
/**
* Constructs a new Pin row.
* @constructor
* @extends {lf.Row.<!veen.row.PinType,
* !veen.row.PinDbType>}
*
* @param {number} rowId The row ID.
* @param {!veen.row.PinType=} opt_payload
*/
veen.row.Pin = function(rowId, opt_payload) {
veen.row.Pin.base(this, 'constructor', rowId, opt_payload);
};
goog.inherits(veen.row.Pin, lf.Row);
/** @override */
veen.row.Pin.prototype.defaultPayload = function() {
var payload = new veen.row.PinType();
payload.id = '';
payload.state = 0;
payload.sessionId = '';
return payload;
};
/** @override */
veen.row.Pin.prototype.toDbPayload = function() {
var payload = new veen.row.PinDbType();
payload.id = this.payload().id;
payload.state = this.payload().state;
payload.sessionId = this.payload().sessionId;
return payload;
};
/** @override */
veen.row.Pin.prototype.keyOfIndex = function(indexName) {
switch (indexName) {
case 'Pin.fkId':
return this.payload().id;
case 'Pin.#':
return this.id();
default:
break;
}
return null;
};
/** @return {string} */
veen.row.Pin.prototype.getId = function() {
return this.payload().id;
};
/**
* @param {string} value
* @return {!veen.row.Pin}
*/
veen.row.Pin.prototype.setId = function(value) {
this.payload().id = value;
return this;
};
/** @return {number} */
veen.row.Pin.prototype.getState = function() {
return this.payload().state;
};
/**
* @param {number} value
* @return {!veen.row.Pin}
*/
veen.row.Pin.prototype.setState = function(value) {
this.payload().state = value;
return this;
};
/** @return {string} */
veen.row.Pin.prototype.getSessionId = function() {
return this.payload().sessionId;
};
/**
* @param {string} value
* @return {!veen.row.Pin}
*/
veen.row.Pin.prototype.setSessionId = function(value) {
this.payload().sessionId = value;
return this;
};
/**
* @extends {lf.schema.Table.<!veen.row.InfoCardType,
* !veen.row.InfoCardDbType>}
* @constructor
*/
veen.schema.InfoCard = function() {
var cols = [];
/** @type {!lf.schema.BaseColumn.<string>} */
this.id = new lf.schema.BaseColumn(
this, 'id', false, false, lf.Type.STRING);
cols.push(this.id);
/** @type {!lf.schema.BaseColumn.<string>} */
this.lang = new lf.schema.BaseColumn(
this, 'lang', false, false, lf.Type.STRING);
cols.push(this.lang);
/** @type {!lf.schema.BaseColumn.<number>} */
this.itag = new lf.schema.BaseColumn(
this, 'itag', false, false, lf.Type.INTEGER);
cols.push(this.itag);
/** @type {!lf.schema.BaseColumn.<string>} */
this.country = new lf.schema.BaseColumn(
this, 'country', false, false, lf.Type.STRING);
cols.push(this.country);
/** @type {!lf.schema.BaseColumn.<string>} */
this.fileName = new lf.schema.BaseColumn(
this, 'fileName', true, false, lf.Type.STRING);
cols.push(this.fileName);
var indices = [
new lf.schema.Index('InfoCard', 'pkInfoCard', true,
[
{schema: this.id, order: lf.Order.ASC, autoIncrement: false},
{schema: this.lang, order: lf.Order.ASC, autoIncrement: false}
]),
new lf.schema.Index('InfoCard', 'uniqFN', true,
[
{schema: this.fileName, order: lf.Order.ASC}
]),
new lf.schema.Index('InfoCard', 'idxPinItag', false,
[
{schema: this.itag, order: lf.Order.ASC}
])
];
/** @private {!lf.schema.Constraint} */
this.constraint_;
veen.schema.InfoCard.base(
this, 'constructor', 'InfoCard', cols, indices, false);
};
goog.inherits(veen.schema.InfoCard, lf.schema.Table);
/** @override */
veen.schema.InfoCard.prototype.createRow = function(opt_value) {
return new veen.row.InfoCard(lf.Row.getNextId(), opt_value);
};
/** @override */
veen.schema.InfoCard.prototype.deserializeRow =
function(dbRecord) {
var data = dbRecord['value'];
return new veen.row.InfoCard(dbRecord['id'], data);
};
/** @override */
veen.schema.InfoCard.prototype.getConstraint = function() {
if (goog.isDefAndNotNull(this.constraint_)) {
return this.constraint_;
}
var pk = this.getIndices()[0];
var notNullable = [
this.id,
this.lang,
this.itag,
this.country,
this.fileName
];
var foreignKeys = [
];
this.constraint_ = new lf.schema.Constraint(
pk, notNullable, foreignKeys);
return this.constraint_;
};
/**
* @export
* @constructor
* @struct
* @final
*/
veen.row.InfoCardType = function() {
/** @export @type {string} */
this.id;
/** @export @type {string} */
this.lang;
/** @export @type {number} */
this.itag;
/** @export @type {string} */
this.country;
/** @export @type {string} */
this.fileName;
};
/**
* @export
* @constructor
* @struct
* @final
*/
veen.row.InfoCardDbType = function() {
/** @export @type {string} */
this.id;
/** @export @type {string} */
this.lang;
/** @export @type {number} */
this.itag;
/** @export @type {string} */
this.country;
/** @export @type {string} */
this.fileName;
};
/**
* Constructs a new InfoCard row.
* @constructor
* @extends {lf.Row.<!veen.row.InfoCardType,
* !veen.row.InfoCardDbType>}
*
* @param {number} rowId The row ID.
* @param {!veen.row.InfoCardType=} opt_payload
*/
veen.row.InfoCard = function(rowId, opt_payload) {
veen.row.InfoCard.base(this, 'constructor', rowId, opt_payload);
};
goog.inherits(veen.row.InfoCard, lf.Row);
/** @override */
veen.row.InfoCard.prototype.defaultPayload = function() {
var payload = new veen.row.InfoCardType();
payload.id = '';
payload.lang = '';
payload.itag = 0;
payload.country = '';
payload.fileName = '';
return payload;
};
/** @override */
veen.row.InfoCard.prototype.toDbPayload = function() {
var payload = new veen.row.InfoCardDbType();
payload.id = this.payload().id;
payload.lang = this.payload().lang;
payload.itag = this.payload().itag;
payload.country = this.payload().country;
payload.fileName = this.payload().fileName;
return payload;
};
/** @override */
veen.row.InfoCard.prototype.keyOfIndex = function(indexName) {
switch (indexName) {
case 'InfoCard.pkInfoCard':
return [
this.payload().id,
this.payload().lang
];
case 'InfoCard.uniqFN':
return this.payload().fileName;
case 'InfoCard.idxPinItag':
return this.payload().itag;
case 'InfoCard.#':
return this.id();
default:
break;
}
return null;
};
/** @return {string} */
veen.row.InfoCard.prototype.getId = function() {
return this.payload().id;
};
/**
* @param {string} value
* @return {!veen.row.InfoCard}
*/
veen.row.InfoCard.prototype.setId = function(value) {
this.payload().id = value;
return this;
};
/** @return {string} */
veen.row.InfoCard.prototype.getLang = function() {
return this.payload().lang;
};
/**
* @param {string} value
* @return {!veen.row.InfoCard}
*/
veen.row.InfoCard.prototype.setLang = function(value) {
this.payload().lang = value;
return this;
};
/** @return {number} */
veen.row.InfoCard.prototype.getItag = function() {
return this.payload().itag;
};
/**
* @param {number} value
* @return {!veen.row.InfoCard}
*/
veen.row.InfoCard.prototype.setItag = function(value) {
this.payload().itag = value;
return this;
};
/** @return {string} */
veen.row.InfoCard.prototype.getCountry = function() {
return this.payload().country;
};
/**
* @param {string} value
* @return {!veen.row.InfoCard}
*/
veen.row.InfoCard.prototype.setCountry = function(value) {
this.payload().country = value;
return this;
};
/** @return {string} */
veen.row.InfoCard.prototype.getFileName = function() {
return this.payload().fileName;
};
/**
* @param {string} value
* @return {!veen.row.InfoCard}
*/
veen.row.InfoCard.prototype.setFileName = function(value) {
this.payload().fileName = value;
return this;
};
goog.provide('veen');
goog.require('lf.Exception');
goog.require('lf.Global');
/** @suppress {extraRequire} */
goog.require('lf.fn');
/** @suppress {extraRequire} */
goog.require('lf.op');
goog.require('lf.proc.Database');
goog.require('lf.service');
goog.require('lf.service.ServiceId');
goog.require('veen.schema.Database');
/**
* @return {!lf.Global} The Global instance that refers to veen.
*/
veen.getGlobal = function() {
var namespacedGlobalId = new lf.service.ServiceId('ns_crdb');
var global = lf.Global.get();
var namespacedGlobal = null;
if (!global.isRegistered(namespacedGlobalId)) {
namespacedGlobal = new lf.Global();
global.registerService(namespacedGlobalId, namespacedGlobal);
} else {
namespacedGlobal = global.getService(namespacedGlobalId);
}
return namespacedGlobal;
};
/** @return {!lf.schema.Database} */
veen.getSchema = function() {
var global = veen.getGlobal();
if (!global.isRegistered(lf.service.SCHEMA)) {
var schema = new veen.schema.Database();
global.registerService(lf.service.SCHEMA, schema);
}
return global.getService(lf.service.SCHEMA);
};
/**
* Whether a DB connection operation is in progress.
* @private {boolean}
*/
veen.connectInProgress_ = false;
/**
* @param {!lf.schema.ConnectOptions=} opt_options
* @return {!IThenable<!lf.proc.Database>}
*/
veen.connect = function(opt_options) {
if (veen.connectInProgress_ ||
(!goog.isNull(veen.db_) && veen.db_.isOpen())) {
// 113: Attempt to connect() to an already connected/connecting database.
throw new lf.Exception(113);
}
veen.connectInProgress_ = true;
if (goog.isNull(veen.db_)) {
veen.getSchema();
veen.db_ = new lf.proc.Database(veen.getGlobal());
}
return veen.db_.init(opt_options).then(
function(db) {
veen.connectInProgress_ = false;
return db;
},
function(e) {
veen.connectInProgress_ = false;
throw e;
});
};
/** @private {?lf.proc.Database} */
veen.db_ = null;
This file is the base one, then you can see that I am using google closure compiler to compile this file together with lovefield sources into one file without goog.
#!/usr/bin/env bash
SCRIPTPATH=$(dirname $BASH_SOURCE)
# Here we will generate js version of schema
${SCRIPTPATH}/spac.sh --schema ${SCRIPTPATH}/../web/app/libs/database/schema.yml --outputdir ${SCRIPTPATH}/../web/app/libs/database/lovefield/ --namespace ahoydb
# and here we take this file that has `goog` and compile it together with lovefield to make 1 file that has both lovefield and your database schema
java -jar ${SCRIPTPATH}/../tools/lovefield/node_modules/google-closure-compiler/compiler.jar --dependency_mode=STRICT --entry_point='ahoydb.row.FlightBooking' --js_output_file=${SCRIPTPATH}/../web/app/libs/database/ahoydb.js "${SCRIPTPATH}/../tools/lovefield/node_modules/google-closure-library/**.js" '!**_test.js' "${SCRIPTPATH}/../tools/lovefield/lib/**.js" "${SCRIPTPATH}/../web/app/libs/database/lovefield/**.js"
Hey, I am using lovefield and from some time to time I am getting this exception:
However, all around app I am using
insertOrReplace
so there is no way I am adding some duplicated keys. You can see that primary key is duplicated somehow, but as I understandinsertOrReplace
it should ensure that it won't duplicate anything right?I am using WebSQL because IndexedDB does not work in Safari. Does anyone know what can cause it or how to prevent it?
Screenshot from Chrome dev tools, you can see there are 2 duplicated keys with value 2560:
Just to explain, I am enforcing that connection happens only once: