commit d211159d741a134d34641fbe75c1da9c6cf2dbac
parent b88b767b0b060fc8bf542a2a652497e91932070c
Author: Dan Stillman <dstillman@zotero.org>
Date: Tue, 14 Mar 2006 11:48:36 +0000
Initial version of data access methods -- documentation will be provided on Basecamp
Surely buggy and incomplete, but a good start
Diffstat:
1 file changed, 1110 insertions(+), 0 deletions(-)
diff --git a/chrome/chromeFiles/content/scholar/data_access.js b/chrome/chromeFiles/content/scholar/data_access.js
@@ -0,0 +1,1110 @@
+/*
+ * Constructor for Object object
+ *
+ * Generally should be called through Scholar_Objects rather than directly
+ */
+function Scholar_Object(){
+ this._init();
+
+ // Accept objectTypeID, folderID and orderIndex in constructor
+ if (arguments.length){
+ this.setType(arguments[0]);
+ this.setPosition(arguments[1],arguments[2]);
+ }
+}
+
+Scholar_Object.prototype._init = function(){
+ //
+ // Public members for access by public methods -- do not access directly
+ //
+ this._data = new Array();
+ this._creators = new Scholar.Hash();
+ this._objectData = new Array();
+
+ this._creatorsLoaded = false;
+ this._objectDataLoaded = false;
+
+ this._changed = new Scholar.Hash();
+ this._changedCreators = new Scholar.Hash();
+ this._changedObjectData = new Scholar.Hash();
+}
+
+/*
+ * Check if the specified field is a primary field from the objects table
+ */
+Scholar_Object.prototype.isPrimaryField = function(field){
+ if (!Scholar_Object.primaryFields){
+ Scholar_Object.primaryFields = Scholar_DB.getColumnHash('objects');
+ }
+
+ return !!Scholar_Object.primaryFields[field];
+}
+
+Scholar_Object.editableFields = {
+ title: true,
+ source: true,
+ rights: true
+};
+
+
+/*
+ * Check if the specified primary field can be changed with setField()
+ */
+Scholar_Object.prototype.isEditableField = function(field){
+ return !!Scholar_Object.editableFields[field];
+}
+
+
+/*
+ * Build object from database
+ */
+Scholar_Object.prototype.loadFromID = function(id){
+ var sql = 'SELECT O.*, lastName AS firstCreator FROM objects O '
+ + 'LEFT JOIN objectCreators OC USING (objectID) '
+ + 'LEFT JOIN creators USING (creatorID) '
+ + 'WHERE objectID=' + id + ' AND OC.orderIndex=1';
+ var row = Scholar_DB.rowQuery(sql);
+ this.loadFromRow(row);
+}
+
+
+/*
+ * Populate basic object data from a database row
+ */
+Scholar_Object.prototype.loadFromRow = function(row){
+ this._init();
+ for (col in row){
+ if (this.isPrimaryField(col) || col=='firstCreator'){
+ this._data[col] = row[col];
+ }
+ }
+ return true;
+}
+
+
+/*
+ * Check if any data fields have changed since last save
+ */
+Scholar_Object.prototype.hasChanged = function(){
+ return (this._changed.length || this._changedCreators.length ||
+ this._changedObjectData.length);
+}
+
+
+Scholar_Object.prototype.getID = function(){
+ return this._data['objectID'] ? this._data['objectID'] : false;
+}
+
+
+Scholar_Object.prototype.getType = function(){
+ return this._data['objectTypeID'] ? this._data['objectTypeID'] : false;
+}
+
+
+/*
+ * Set or change the object's type
+ */
+Scholar_Object.prototype.setType = function(objectTypeID){
+ if (objectTypeID==this.getType()){
+ return true;
+ }
+
+ // If existing type, clear fields from old type that aren't in new one
+ if (this.getType()){
+ var sql = 'SELECT fieldID FROM objectTypeFields '
+ + 'WHERE objectTypeID=' + this.getType() + ' AND fieldID NOT IN '
+ + '(SELECT fieldID FROM objectTypeFields WHERE objectTypeID='
+ + objectTypeID + ')';
+ var obsoleteFields = Scholar_DB.columnQuery(sql);
+
+ if (obsoleteFields){
+ for (var i=0; i<obsoleteFields.length; i++){
+ this.setField(obsoleteFields[i],false);
+ }
+ }
+ }
+
+ this._data['objectTypeID'] = objectTypeID;
+ this._changed.set('objectTypeID');
+ return true;
+}
+
+
+/*
+ * Returns the number of creators for this object
+ */
+Scholar_Object.prototype.numCreators = function(){
+ if (this.getID() && !this._creatorsLoaded){
+ this._loadCreators();
+ }
+ return this._creators.length;
+}
+
+/*
+ * Returns an array of the creator data at the given position, or false if none
+ */
+Scholar_Object.prototype.getCreator = function(pos){
+ if (this.getID() && !this._creatorsLoaded){
+ this._loadCreators();
+ }
+
+ if (!this._creators.items[pos]){
+ return false;
+ }
+ return this._creators.items[pos];
+}
+
+
+/*
+ * Set or update the creator at the specified position
+ */
+Scholar_Object.prototype.setCreator = function(orderIndex, firstName, lastName, creatorTypeID){
+ if (this.getID() && !this._creatorsLoaded){
+ this._loadCreators();
+ }
+
+ if (!creatorTypeID){
+ creatorTypeID = 1;
+ }
+
+ if (!firstName && !lastName){
+ throw ('Name not provided for creator');
+ }
+
+ if (this._creators.has(orderIndex) &&
+ this._creators.get(orderIndex)['firstName']==firstName &&
+ this._creators.get(orderIndex)['lastName']==lastName &&
+ this._creators.get(orderIndex)['creatorTypeID']==creatorTypeID){
+ return true;
+ }
+
+ var creator = new Array();
+ creator['firstName'] = firstName;
+ creator['lastName'] = lastName;
+ creator['creatorTypeID'] = creatorTypeID;
+
+ this._creators.set(orderIndex, creator);
+ this._changedCreators.set(orderIndex);
+ return true;
+}
+
+
+/*
+ * Remove a creator and shift others down
+ */
+Scholar_Object.prototype.removeCreator = function(orderIndex){
+ if (this.getID() && !this._creatorsLoaded){
+ this._loadCreators();
+ }
+
+ if (!this._creators.has(orderIndex)){
+ throw ('No creator exists at position ' + orderIndex);
+ }
+ this._creators.remove(orderIndex);
+
+ for (var i=orderIndex,len=this._creators.length; i<=len; i++){
+ var next =
+ this._creators.items[i+1] ? this._creators.items[i+1] : false;
+ this._creators.set(i, next);
+ this._changedCreators.set(i);
+ }
+ return true;
+}
+
+
+/*
+ * Retrieves (and loads from DB, if necessary) an objectData field value
+ *
+ * Field can be passed as fieldID or fieldName
+ */
+Scholar_Object.prototype.getField = function(field){
+ if (this.isPrimaryField(field)){
+ return this._data[field] ? this._data[field] : false;
+ }
+ else {
+ if (this.getID() && !this._objectDataLoaded){
+ this._loadObjectData();
+ }
+
+ var fieldID = Scholar_ObjectFields.getID(field);
+
+ return this._objectData[fieldID] ? this._objectData[fieldID] : false;
+ }
+}
+
+
+/*
+ * Set a field value, loading existing objectData first if necessary
+ *
+ * Field can be passed as fieldID or fieldName
+ */
+Scholar_Object.prototype.setField = function(field, value, loadIn){
+ // Primary field
+ if (this.isPrimaryField(field)){
+ if (!this.isEditableField()){
+ throw ('Primary field ' + field + ' cannot be changed through ' +
+ 'setField');
+ }
+
+ if (this._data[field] && this._data[field]==value){
+ return false;
+ }
+ this._data[field] = value;
+ this._changed.set(field);
+ return true;
+ }
+ // Type-specific field
+ else {
+ if (!this.getType()){
+ throw ('Object type must be set before setting field data.');
+ }
+
+ // If existing object, load field data first unless we're already in
+ // the middle of a load
+ if (this.getID() && !loadIn && !this._objectDataLoaded){
+ this._loadObjectData();
+ }
+
+ var fieldID = Scholar_ObjectFields.getID(field);
+
+ if (!fieldID){
+ throw (field + ' is not a valid objectData field.');
+ }
+
+ if (!Scholar_ObjectFields.isValidForType(fieldID, this.getType())){
+ throw (field + ' is not a valid field for this type.');
+ }
+
+ // If existing value, make sure it's actually changing
+ if (this._objectData[fieldID] && this._objectData[fieldID]==value){
+ return false;
+ }
+ this._objectData[fieldID] = value;
+ if (!loadIn){
+ this._changedObjectData.set(fieldID);
+ }
+ return true;
+ }
+}
+
+
+/*
+ * Move object to new position and shift surrounding objects
+ *
+ * N.B. This action updates the DB immediately and reloads all cached
+ * objects -- a save() is not required
+ */
+Scholar_Object.prototype.setPosition = function(newFolder, newPos){
+ var oldFolder = this.getField('folderID');
+ var oldPos = this.getField('orderIndex');
+
+ if (this.getID()){
+ if (newFolder==oldFolder && newPos==oldPos){
+ return true;
+ }
+ var sql = "BEGIN;\n";
+
+ // If no position provided, drop at end of folder
+ if (!newPos){
+ newPos = Scholar_DB.valueQuery('SELECT MAX(orderIndex)+1 FROM ' +
+ 'objects WHERE folderID=' + newFolder);
+ }
+ // Otherwise shift down above it in old folder and shift up at it or
+ // above it in new folder
+ else {
+ sql += 'UPDATE objects SET orderIndex=orderIndex-1 WHERE folderID='
+ + oldFolder + ' AND orderIndex>' + oldPos + ";\n";
+
+ sql += 'UPDATE objects SET orderIndex=orderIndex+1 WHERE folderID='
+ + newFolder + ' AND orderIndex>=' + newPos + ";\n";
+ }
+
+ sql += 'UPDATE objects SET folderID=' + newFolder + ', orderIndex=' +
+ newPos + ' WHERE objectID=' + this.getID() + ";\n";
+
+ sql += 'COMMIT;';
+
+ Scholar_DB.query(sql);
+ }
+
+ this._data['folderID'] = newFolder;
+ this._data['orderIndex'] = newPos;
+ Scholar_Objects.reloadAll();
+ return true;
+}
+
+
+/*
+ * Save changes back to database
+ */
+Scholar_Object.prototype.save = function(){
+ if (!this.hasChanged()){
+ Scholar.debug('Object ' + this.getID() + ' has not changed', 4);
+ return !!this.getID();
+ }
+
+ //
+ // Existing object, update
+ //
+ if (this.getID()){
+ Scholar.debug('Updating database with new object data', 4);
+
+ var objectID = this.getID();
+
+ try {
+ Scholar_DB.beginTransaction();
+
+ //
+ // Primary fields
+ //
+ var sql = "UPDATE objects SET ";
+ var sql2;
+
+ if (this._changed.has('objectTypeID')){
+ sql += "objectTypeID='" + this.getField('objectTypeID') + "', ";
+ }
+ if (this._changed.has('title')){
+ sql += "title='" + this.getField('title') + "', ";
+ }
+ if (this._changed.has('source')){
+ sql += "source='" + this.getField('source') + "', ";
+ }
+ if (this._changed.has('rights')){
+ sql += "rights='" + this.getField('rights') + "', ";
+ }
+
+ // Always update modified time
+ sql += "dateModified=CURRENT_TIMESTAMP ";
+ sql += "WHERE objectID=" + this.getID() + ";\n";
+
+
+ //
+ // Creators
+ //
+ if (this._changedCreators.length){
+ for (orderIndex in this._changedCreators.items){
+ Scholar.debug('Creator ' + orderIndex + ' has changed', 4);
+
+ var creator = this.getCreator(orderIndex);
+
+ // If empty, delete at position and shift down any above it
+ //
+ // We have to do this immediately so old entries are
+ // cleared before other ones are shifted down
+ if (!creator['firstName'] && !creator['lastName']){
+ sql2 = 'DELETE FROM objectCreators '
+ + ' WHERE objectID=' + this.getID()
+ + ' AND orderIndex=' + orderIndex;
+ Scholar_DB.query(sql2);
+ continue;
+ }
+
+ // See if this is an existing creator
+ var creatorID = Scholar_Creators.getID(
+ creator['firstName'],
+ creator['lastName'],
+ creator['creatorTypeID']
+ );
+
+ // If not, add it
+ if (!creatorID){
+ creatorID = Scholar_Creators.add(
+ creator['firstName'],
+ creator['lastName'],
+ creator['creatorTypeID']
+ );
+ }
+
+
+ sql2 = 'SELECT COUNT(*) FROM objectCreators'
+ + ' WHERE objectID=' + this.getID()
+ + ' AND orderIndex=' + orderIndex;
+
+ if (Scholar_DB.valueQuery(sql2)){
+ sql += 'UPDATE objectCreators SET creatorID='
+ + creatorID + ' WHERE objectID=' + this.getID()
+ + ' AND orderIndex=' + orderIndex + ";\n";
+ }
+ else {
+ sql += 'INSERT INTO objectCreators VALUES ('
+ + creatorID + ',' + objectID + ',' + orderIndex
+ + ");\n";
+ }
+ }
+
+ // Append the SQL to delete obsolete creators
+ sql += Scholar_Creators.purge(true) + "\n";
+ }
+
+
+ //
+ // ObjectData
+ //
+ if (this._changedObjectData.length){
+ var del = new Array();
+ for (fieldID in this._changedObjectData.items){
+ if (this.getField(fieldID)){
+ // Oh, for an INSERT...ON DUPLICATE KEY UPDATE
+ sql2 = 'SELECT COUNT(*) FROM objectData '
+ + 'WHERE objectID=' + this.getID()
+ + ' AND fieldID=' + fieldID;
+
+ if (Scholar_DB.valueQuery(sql2)){
+ sql += "UPDATE objectData SET value=";
+ // Take advantage of SQLite's manifest typing
+ if (Scholar_ObjectFields.isInteger(fieldID)){
+ sql += this.getField(fieldID);
+ }
+ else {
+ sql += "'" + this.getField(fieldID) + "'";
+ }
+ sql += " WHERE objectID=" + this.getID()
+ + ' AND fieldID=' + fieldID + ";\n";
+ }
+ else {
+ sql += 'INSERT INTO objectData VALUES ('
+ + this.getID() + ',' + fieldID + ',';
+
+ if (Scholar_ObjectFields.isInteger(fieldID)){
+ sql += this.getField(fieldID);
+ }
+ else {
+ sql += "'" + this.getField(fieldID) + "'";
+ }
+ sql += ");\n";
+ }
+ }
+ // If field changed and is empty, mark row for deletion
+ else {
+ del.push(fieldID);
+ }
+ }
+
+ // Delete blank fields
+ if (del.length){
+ sql += 'DELETE from objectData '
+ + 'WHERE objectID=' + this.getID() + ' '
+ + 'AND fieldID IN (' + del.join() + ");\n";
+ }
+ }
+
+
+ Scholar_DB.query(sql);
+ Scholar_DB.commitTransaction();
+ }
+ catch (e){
+ Scholar_DB.rollbackTransaction();
+ throw (e);
+ }
+ }
+
+ //
+ // New object, insert and return id
+ //
+ else {
+ Scholar.debug('Saving data for new object to database');
+
+ var isNew = true;
+ var sqlColumns = new Array();
+ var sqlValues = new Array();
+
+ //
+ // Primary fields
+ //
+ sqlColumns.push('objectTypeID');
+ sqlValues.push({'int':this.getField('objectTypeID')});
+
+ if (this._changed.has('title')){
+ sqlColumns.push('title');
+ sqlValues.push({'string':this.getField('title')});
+ }
+ if (this._changed.has('source')){
+ sqlColumns.push('source');
+ sqlValues.push({'string':this.getField('source')});
+ }
+ if (this._changed.has('rights')){
+ sqlColumns.push('rights');
+ sqlValues.push({'string':this.getField('rights')});
+ }
+
+ sqlColumns.push('folderID');
+ var newFolder =
+ this._changed.has('folderID') ? this.getField('folderID') : 0;
+ sqlValues.push({'int':newFolder});
+
+ try {
+ Scholar_DB.beginTransaction();
+
+ // We set the index here within the transaction so that MAX()+1
+ // stays consistent through the INSERT
+ sqlColumns.push('orderIndex');
+ if (this._changed.has('orderIndex')){
+ sqlValues.push({'int':this.getField('orderIndex')});
+ }
+ else {
+ var newPos = Scholar_DB.valueQuery('SELECT MAX(orderIndex)+1 '
+ + 'FROM objects WHERE folderID=' + newFolder);
+ sqlValues.push({'int': newPos});
+ }
+
+
+ //
+ // Creators
+ //
+ if (this._changedCreators.length){
+ for (orderIndex in this._changedCreators.items){
+ var creator = this.getCreator(orderIndex);
+
+ // If empty, skip
+ if (typeof creator['firstName'] == 'undefined'
+ && typeof creator['lastName'] == 'undefined'){
+ continue;
+ }
+
+ // See if this is an existing creator
+ var creatorID = Scholar_Creators.getID(
+ creator['firstName'],
+ creator['lastName'],
+ creator['creatorTypeID']
+ );
+
+ // If not, add it
+ if (!creatorID){
+ creatorID = Scholar_Creators.add(
+ creator['firstName'],
+ creator['lastName'],
+ creator['creatorTypeID']
+ );
+ }
+
+ sql += 'INSERT INTO objectCreators VALUES ('
+ + creatorID + ',' + objectID + ',' + orderIndex
+ + ");\n";
+ }
+ }
+
+
+ //
+ // objectData fields
+ //
+ var sql = "INSERT INTO objects (" + sqlColumns.join() + ')'
+ + ' VALUES (';
+ // Insert placeholders for bind parameters
+ for (var i=0; i<sqlValues.length; i++){
+ sql += '?,';
+ }
+ sql = sql.substring(0,sql.length-1) + ");\n";
+
+ var objectID = Scholar_DB.query(sql,sqlValues);
+
+ if (this._changedObjectData.length){
+ sql = '';
+ for (fieldID in this._changedObjectData.items){
+ sql += 'INSERT INTO objectData VALUES (' +
+ objectID + ',' + fieldID + ',';
+ if (Scholar_ObjectFields.isInteger(fieldID)){
+ sql += this.getField(fieldID);
+ }
+ else {
+ sql += "'" + this.getField(fieldID) + "'";
+ }
+ sql += ");\n";
+ }
+ }
+
+ Scholar_DB.query(sql);
+ Scholar_DB.commitTransaction();
+ }
+ catch (e){
+ Scholar_DB.rollbackTransaction();
+ throw (e);
+ }
+ }
+
+ Scholar_Objects.reload(this.getID());
+
+ return isNew ? this.getID() : true;
+}
+
+
+Scholar_Object.prototype.toString = function(){
+ return this.getTitle();
+}
+
+
+/*
+ * Load in the creators from the database
+ */
+Scholar_Object.prototype._loadCreators = function(){
+ if (!this.getID()){
+ throw ('ObjectID not set for object before attempting to load creators');
+ }
+
+ var sql = 'SELECT C.creatorID, C.*, orderIndex FROM objectCreators OC '
+ + 'LEFT JOIN creators C USING (creatorID) '
+ + 'WHERE objectID=' + this.getID() + ' ORDER BY orderIndex';
+ var creators = Scholar_DB.query(sql);
+
+ if (!creators){
+ return true;
+ }
+
+ this._creators = new Scholar.Hash();
+ for (var i=0; i<creators.length; i++){
+ var creator = new Array();
+ creator['firstName'] = creators[i]['firstName'];
+ creator['lastName'] = creators[i]['lastName'];
+ creator['creatorTypeID'] = creators[i]['creatorTypeID'];
+ this._creators.set(creators[i]['orderIndex'], creator);
+ }
+
+ this._creatorsLoaded = true;
+ return true;
+}
+
+
+/*
+ * Load in the field data from the database
+ */
+Scholar_Object.prototype._loadObjectData = function(){
+ if (!this.getID()){
+ throw ('ObjectID not set for object before attempting to load data');
+ }
+
+ var sql = 'SELECT OD.fieldID, value FROM objectData OD JOIN '
+ + 'objectTypeFields OTF ON (OTF.objectTypeID=(SELECT objectTypeID FROM '
+ + 'objects WHERE objectID=?1) AND OTF.fieldID=OD.fieldID) '
+ + 'WHERE objectID=?1 ORDER BY orderIndex';
+
+ var result = Scholar_DB.query(sql,[{'int':this._data['objectID']}]);
+
+ if (result){
+ for (var i=0,len=result.length; i<len; i++){
+ this.setField(result[i]['fieldID'], result[i]['value'], true);
+ }
+ this._objectDataLoaded = true;
+ return true;
+ }
+ else {
+ return false;
+ }
+}
+
+
+
+
+
+var Scholar_Objects = new function(){
+ // Private members
+ var _objects = new Array();
+
+ // Privileged methods
+ this.get = get;
+ this.getAll = getAll;
+ this.reload = reload;
+ this.reloadAll = reloadAll;
+
+ /*
+ * Retrieves (and loads, if necessary) an arbitrary number of objects
+ *
+ * Can be passed ids as individual parameters or as an array of ids, or both
+ */
+ function get(){
+ var toLoad = new Array();
+ var loaded = new Array();
+
+ if (!arguments[0]){
+ return false;
+ }
+
+ var ids = Scholar.flattenArguments(arguments);
+
+ for (var i=0; i<ids.length; i++){
+ // Check if already loaded
+ if (!_objects[ids[i]]){
+ toLoad.push(ids[i]);
+ }
+ }
+
+ // New objects to load
+ if (toLoad.length){
+ _load(toLoad);
+ }
+
+ // Build return array
+ for (i=0; i<ids.length; i++){
+ loaded[ids[i]] = _objects[ids[i]];
+ }
+
+ return loaded;
+ }
+
+
+ /*
+ * Returns all objects in the database
+ */
+ function getAll(){
+ var sql = 'SELECT O.objectID FROM objects O '
+ + 'LEFT JOIN objectCreators OC USING (objectID) '
+ + 'LEFT JOIN creators C USING (creatorID) '
+ + 'LEFT JOIN folders F ON (O.folderID=F.folderID) '
+
+ // Only get first creator
+ + 'WHERE OC.orderIndex=1 '
+
+ // folderID=0 puts root folder items after folders
+ // TODO: allow folders to intermingle with items, order-wise
+ + 'ORDER BY O.folderID=0, F.orderIndex, O.orderIndex';
+
+ var ids = Scholar_DB.columnQuery(sql);
+ return this.get(ids);
+ }
+
+
+ /*
+ * Reloads data for specified objects into internal array
+ *
+ * Can be passed ids as individual parameters or as an array of ids, or both
+ */
+ function reload(){
+ if (!arguments[0]){
+ return false;
+ }
+
+ var ids = Scholar.flattenArguments(arguments);
+ Scholar.debug('Reloading ' + ids);
+ _load(ids);
+
+ return true;
+ }
+
+
+ /*
+ * Reloads all currently cached objects
+ */
+ function reloadAll(){
+ var ids = new Array();
+ for (objectID in _objects){
+ ids.push(objectID);
+ }
+ _load(ids);
+ return true;
+ }
+
+
+ function add(data, objectTypeID, folderID, orderIndex){
+ var insert = new Array();
+
+ var obj = new Scholar_Object(objectTypeID, folderID, orderIndex);
+
+ for (field in data){
+ obj.setField(data[field]);
+ }
+
+ var id = obj.save();
+
+ return this.get(id);
+ }
+
+
+ function _load(){
+ if (!arguments){
+ return false;
+ }
+
+ var sql = 'SELECT O.*, lastName AS firstCreator FROM objects O '
+ + 'LEFT JOIN objectCreators OC USING (objectID) '
+ + 'LEFT JOIN creators USING (creatorID) '
+ + 'WHERE OC.orderIndex=1';
+
+ if (arguments[0]!='all'){
+ sql += ' AND O.objectID IN (' + Scholar.join(arguments,',') + ')';
+ }
+
+ var result = Scholar_DB.query(sql);
+
+ if (result){
+ for (var i=0,len=result.length; i<len; i++){
+ var obj = new Scholar_Object();
+ obj.loadFromRow(result[i]);
+ _objects[result[i]['objectID']] = obj;
+ }
+ }
+ return true;
+ }
+}
+
+
+var Scholar_Creators = new function(){
+ var _creators = new Array; // indexed by first%%%last%%%creatorTypeID hash
+ var _creatorsByID = new Array; // indexed by creatorID
+
+ this.get = get;
+ this.getID = getID;
+ this.add = add;
+ this.purge = purge;
+
+ /*
+ * Returns an array of creator data for a given creatorID
+ */
+ function get(creatorID){
+ if (_creatorsByID[creatorID]){
+ return _creatorsByID[creatorID];
+ }
+
+ var sql = 'SELECT * FROM creators WHERE creatorID=' + creatorID;
+ var result = Scholar_DB.rowQuery(sql);
+
+ if (!result){
+ return false;
+ }
+
+ _creatorsByID[creatorID] = result;
+ return result;
+ }
+
+
+ /*
+ * Returns the creatorID matching given name and type
+ */
+ function getID(firstName, lastName, creatorTypeID){
+ var hash = firstName + '%%%' + lastName + '%%%' + creatorTypeID;
+
+ if (_creators[hash]){
+ return _creators[hash];
+ }
+
+ var sql = 'SELECT creatorID FROM creators WHERE firstName=? AND '
+ + 'lastName=? AND creatorTypeID=?';
+ var params = [
+ {'string': firstName}, {'string': lastName}, {'int': creatorTypeID}
+ ];
+ var creatorID = Scholar_DB.valueQuery(sql,params);
+
+ if (creatorID){
+ _creators[hash] = creatorID;
+ }
+
+ return creatorID;
+ }
+
+
+ /*
+ * Add a new creator to the database
+ *
+ * Returns new creatorID
+ */
+ function add(firstName, lastName, creatorTypeID){
+ Scholar.debug('Adding new creator', 4);
+
+ var sql = 'INSERT INTO creators '
+ + 'VALUES (?,?,?,?)';
+
+ // Use a random integer for the creatorID
+ var tries = 10; // # of tries to find a unique id
+ var max = 65535;
+ do {
+ // If no luck after 10 tries, try a larger range
+ if (!tries){
+ tries = 10;
+ max = max * 10;
+ }
+ var rnd = Math.floor(Math.random()*max);
+ var sql2 = 'SELECT COUNT(*) FROM creators WHERE creatorID=' + rnd;
+ var exists = Scholar_DB.valueQuery(sql2);
+ tries--;
+ }
+ while (exists);
+
+ var params = [
+ {'int': rnd}, {'int': creatorTypeID},
+ {'string': firstName}, {'string': lastName},
+ ];
+ return Scholar_DB.query(sql,params);
+ }
+
+
+ /*
+ * Delete obsolete creators from database and clear internal array entries
+ *
+ * Returns TRUE on success, or SQL query to run in returnSQL mode
+ */
+ function purge(returnSQL){
+ var sql = 'SELECT creatorID FROM creators WHERE creatorID NOT IN '
+ + '(SELECT creatorID FROM objectCreators);';
+ var toDelete = Scholar_DB.columnQuery(sql);
+
+ if (!toDelete){
+ return false;
+ }
+
+ sql = 'DELETE FROM creators WHERE creatorID NOT IN '
+ + '(SELECT creatorID FROM objectCreators);';
+
+ if (!returnSQL){
+ var result = Scholar_DB.query(sql);
+ }
+
+ // Clear creator entries in internal array
+ for (var i=0; i<toDelete.length; i++){
+ var hash = _getHash(toDelete[i]);
+ delete _creators[hash];
+ delete _creatorsByID[toDelete[i]];
+ }
+
+ return returnSQL ? sql : result;
+ }
+
+
+ function _getHash(creatorID){
+ var creator = this.get(creatorID);
+ if (!creator){
+ return false;
+ }
+ return creator['firstName'] + '%%%' + creator['lastName']
+ + '%%%' + creator['creatorTypeID'];
+ }
+}
+
+
+var Scholar_ObjectFields = new function(){
+ // Private members
+ var _fields = new Array();
+ var _fieldFormats = new Array();
+ var _objectTypeFields = new Array();
+
+ // Privileged methods
+ this.getName = getName;
+ this.getID = getID;
+ this.isValidForType = isValidForType;
+ this.isInteger = isInteger;
+ this.getObjectTypeFields = getObjectTypeFields;
+
+ /*
+ * Return the fieldName for a passed fieldID or fieldName
+ */
+ function getName(field){
+ if (!_fields.length){
+ _loadFields();
+ }
+ return _fields[field] ? _fields[field]['name'] : false;
+ }
+
+
+ /*
+ * Return the fieldID for a passed fieldID or fieldName
+ */
+ function getID(field){
+ if (!_fields.length){
+ _loadFields();
+ }
+ return _fields[field] ? _fields[field]['id'] : false;
+ }
+
+
+ function isValidForType(fieldID, objectTypeID){
+ if (!_fields.length){
+ _loadFields();
+ }
+ return !!_fields[fieldID]['objectTypes'][objectTypeID];
+ }
+
+
+ function isInteger(fieldID){
+ if (!_fields.length){
+ _loadFields();
+ }
+ var ffid = _fields[fieldID]['formatID'];
+ return _fieldFormats[ffid] ? _fieldFormats[ffid]['isInteger'] : false;
+ }
+
+
+ /*
+ * Returns an array of fieldIDs for a given object type
+ */
+ function getObjectTypeFields(objectTypeID){
+ if (_objectTypeFields[objectTypeID]){
+ return _objectTypeFields[objectTypeID];
+ }
+
+ var sql = 'SELECT fieldID FROM objectTypeFields '
+ + 'WHERE objectTypeID=' + objectTypeID + ' ORDER BY orderIndex';
+
+ _objectTypeFields[objectTypeID] = Scholar_DB.columnQuery(sql);
+ return _objectTypeFields[objectTypeID];
+ }
+
+
+ /*
+ * Returns hash array of objectTypeIDs for which a given field is valid
+ */
+ function _getFieldObjectTypes(){
+ var sql = 'SELECT fieldID,objectTypeID FROM objectTypeFields';
+
+ var results = Scholar_DB.query(sql);
+
+ if (!results){
+ throw ('No fields in objectTypeFields!');
+ }
+ var fields = new Array();
+ for (var i=0; i<results.length; i++){
+ if (!fields[results[i]['fieldID']]){
+ fields[results[i]['fieldID']] = new Array();
+ }
+ fields[results[i]['fieldID']][results[i]['objectTypeID']] = true;
+ }
+ return fields;
+ }
+
+
+ /*
+ * Load all fields into an internal hash array
+ */
+ function _loadFields(){
+ var i,len;
+
+ var result = Scholar_DB.query('SELECT * FROM fieldFormats');
+
+ for (i=0; i<result.length; i++){
+ _fieldFormats[result[i]['fieldFormatID']] = {
+ regex: result[i]['regex'],
+ isInteger: result[i]['isInteger']
+ };
+ }
+
+ result = Scholar_DB.query('SELECT * FROM fields');
+
+ if (!result.length){
+ throw ('No fields in database!');
+ }
+
+ var fieldObjectTypes = _getFieldObjectTypes();
+
+ for (i=0,len=result.length; i<len; i++){
+ _fields[result[i]['fieldID']] = {
+ id: result[i]['fieldID'],
+ name: result[i]['fieldName'],
+ formatID: result[i]['fieldFormatID'],
+ objectTypes: fieldObjectTypes[result[i]['fieldID']]
+ };
+ // Store by name as well as id
+ _fields[result[i]['fieldName']] = _fields[result[i]['fieldID']];
+ }
+ }
+}
+
+/*
+var objects = Scholar_Objects.getAll();
+
+var obj = objects[9];
+for (var i=0,len=obj.numCreators(); i<len; i++){
+ Scholar.debug(Scholar.varDump(obj.getCreator(i)));
+}
+
+obj.setCreator(2,'bob','smith');
+
+for (var i=0,len=obj.numCreators(); i<len; i++){
+ Scholar.debug(Scholar.varDump(obj.getCreator(i)));
+}
+obj.save();
+*/