Moved title field to itemData table, which made the following possible:

- All remaining fields in the items table exists in all items and are non-user-editable
   - Simplified some data access code (e.g. removed Item.isEditableField())
   - 'title' is now a base field used in case (Case Name), statute (nameOfAct) and e-mail (Subject)

Reengineered parts of the data layer for better performance

   - Various recent changes, including the 'title' change above and base field mapping in Item.getField(), had a negative effect on performance. This should help. In particular, itemData values are now loaded in in bulk by Items._load() (via Items.get()) rather than on-demand, the sort process in itemTreeView caches values while sorting, and ItemFields.getFieldIDFromTypeAndBase() is faster.


Addresses #346, mapping for new item types
This commit is contained in:
Dan Stillman 2007-03-10 06:44:39 +00:00
parent 3465346694
commit 358924de47
6 changed files with 741 additions and 691 deletions

View File

@ -278,15 +278,16 @@ var ZoteroItemPane = new function()
if(_itemTypeMenu.firstChild.childNodes[i].value == _itemBeingEdited.getType())
_itemTypeMenu.selectedIndex = i;
var fieldNames = new Array("title");
var fieldNames = [];
var fields = Zotero.ItemFields.getItemTypeFields(_itemBeingEdited.getField("itemTypeID"));
for(var i = 0; i<fields.length; i++)
for (var i = 0; i<fields.length; i++) {
fieldNames.push(Zotero.ItemFields.getName(fields[i]));
}
fieldNames.push("dateAdded","dateModified");
for(var i = 0; i<fieldNames.length; i++)
{
var editable = (!_itemBeingEdited.isPrimaryField(fieldNames[i]) || _itemBeingEdited.isEditableField(fieldNames[i]));
var editable = !_itemBeingEdited.isPrimaryField(fieldNames[i]);
var val = _itemBeingEdited.getField(fieldNames[i]);

View File

@ -48,6 +48,7 @@ Zotero.Item.prototype._init = function(){
this._changedCreators = new Zotero.Hash();
this._changedItemData = new Zotero.Hash();
this._noteTitle = null;
this._noteText = null;
this._noteAccessTime = null;
@ -77,18 +78,6 @@ Zotero.Item.prototype.isPrimaryField = function(field){
return !!Zotero.Item.primaryFields[field];
}
Zotero.Item.editableFields = {
title: true
};
/*
* Check if the specified primary field can be changed with setField()
*/
Zotero.Item.prototype.isEditableField = function(field){
return !!Zotero.Item.editableFields[field];
}
/*
* Build object from database
*/
@ -115,14 +104,9 @@ Zotero.Item.prototype.loadFromID = function(id){
*/
Zotero.Item.prototype.loadFromRow = function(row){
this._init();
for (col in row){
for (var col in row){
// Only accept primary field data through loadFromRow()
if (this.isPrimaryField(col)){
// Return first line of content for note items
if (col=='title' && this.isNote()){
row[col] = this._noteToTitle();
}
this._data[col] = row[col];
}
else {
@ -436,17 +420,27 @@ Zotero.Item.prototype.getField = function(field, unformatted, includeBaseMapped)
if (this.isPrimaryField(field)){
return this._data[field] ? this._data[field] : '';
}
else {
if (Zotero.ItemFields.getName(field) == 'title' && this.isNote()) {
if (this._noteTitle !== null) {
return this._noteTitle;
}
var title = this._noteToTitle();
this._noteTitle = title;
return title;
}
if (this.getID() && !this._itemDataLoaded){
this._loadItemData();
}
if (includeBaseMapped && Zotero.ItemFields.isBaseField(field)) {
if (includeBaseMapped) {
var fieldID = Zotero.ItemFields.getFieldIDFromTypeAndBase(
this.getType(), field
);
}
else {
if (!fieldID) {
var fieldID = Zotero.ItemFields.getID(field);
}
@ -460,7 +454,6 @@ Zotero.Item.prototype.getField = function(field, unformatted, includeBaseMapped)
return value;
}
}
/*
@ -475,22 +468,10 @@ Zotero.Item.prototype.setField = function(field, value, loadIn){
// Primary field
if (this.isPrimaryField(field)){
if (!this.isEditableField(field)){
throw ('Primary field ' + field + ' cannot be changed through ' +
'setField');
throw ('Primary field ' + field + ' cannot be changed through setField');
}
if (this._data[field] != undefined && this._data[field]==value){
return false;
}
this._data[field] = value;
if (!loadIn){
this._changed.set(field);
}
return true;
}
// Type-specific field
else {
if (!this.getType()){
throw ('Item type must be set before setting field data.');
}
@ -535,12 +516,17 @@ Zotero.Item.prototype.setField = function(field, value, loadIn){
return false;
}
this._itemData[fieldID] = value;
if (!loadIn){
if (loadIn) {
// Not ideal to do this here, but there's not a great way to set this
// private variable from Z.Items._load()
this._itemDataLoaded = true;
}
else {
this._changedItemData.set(fieldID);
}
return true;
}
}
/*
@ -596,10 +582,6 @@ Zotero.Item.prototype.save = function(){
sql += "itemTypeID=?, ";
sqlValues.push({'int':this.getField('itemTypeID')});
}
if (this._changed.has('title')){
sql += "title=?, ";
sqlValues.push({'string':this.getField('title')});
}
// Always update modified time
sql += "dateModified=CURRENT_TIMESTAMP ";
@ -827,11 +809,6 @@ Zotero.Item.prototype.save = function(){
sqlColumns.push('itemTypeID');
sqlValues.push({'int':this.getField('itemTypeID')});
if (this._changed.has('title')){
sqlColumns.push('title');
sqlValues.push({'string':this.getField('title')});
}
try {
Zotero.DB.beginTransaction();
@ -1065,7 +1042,7 @@ Zotero.Item.prototype.updateNoteCache = function(text){
// Update cached values
this._noteText = text ? text : '';
if (this.isNote()){
this.setField('title', this._noteToTitle(), true);
this._noteTitle = this._noteToTitle();
}
}
@ -1900,13 +1877,6 @@ Zotero.Item.prototype.clone = function() {
for (var i in obj) {
switch (i) {
// TODO: remove when title is changed to regular field
case 'title':
if (obj.itemType != 'note') {
newItem.setField('title', obj[i]);
}
continue;
case 'creators':
var i = 0;
for each(var c in obj.creators) {
@ -2176,8 +2146,6 @@ Zotero.Item.prototype.toArray = function(){
// Notes
if (this.isNote()) {
// Don't need title for notes
delete arr['title'];
arr['note'] = this.getNote();
if (this.getSource()){
arr['sourceItemID'] = this.getSource();
@ -2268,24 +2236,14 @@ Zotero.Item.prototype._loadItemData = function(){
throw ('ItemID not set for object before attempting to load data');
}
var sql = 'SELECT ID.fieldID, value FROM itemData ID JOIN '
+ 'itemTypeFields ITF ON (ITF.itemTypeID=(SELECT itemTypeID FROM '
+ 'items WHERE itemID=?1) AND ITF.fieldID=ID.fieldID) '
+ 'WHERE itemID=?1 ORDER BY orderIndex';
var sql = 'SELECT fieldID, value FROM itemData WHERE itemID=?';
var fields = Zotero.DB.query(sql, this.getID());
var result = Zotero.DB.query(sql,[{'int':this._data['itemID']}]);
for each(var field in fields) {
this.setField(field['fieldID'], field['value'], true);
}
this._itemDataLoaded = true;
if (result){
for (var i=0,len=result.length; i<len; i++){
this.setField(result[i]['fieldID'], result[i]['value'], true);
}
return true;
}
else {
return false;
}
}
@ -2516,22 +2474,30 @@ Zotero.Items = new function(){
if (arguments[0]){
sql += ' AND I.itemID IN (' + Zotero.join(arguments,',') + ')';
}
var result = Zotero.DB.query(sql);
var itemsRows = Zotero.DB.query(sql);
if (result){
for (var i=0,len=result.length; i<len; i++){
for each(var row in itemsRows) {
// Item doesn't exist -- create new object and stuff in array
if (!_items[result[i]['itemID']]){
var obj = new Zotero.Item();
obj.loadFromRow(result[i]);
_items[result[i]['itemID']] = obj;
if (!_items[row['itemID']]){
var item = new Zotero.Item();
item.loadFromRow(row);
_items[row['itemID']] = item;
}
// Existing item -- reload in place
else {
_items[result[i]['itemID']].loadFromRow(result[i]);
_items[row['itemID']].loadFromRow(row);
}
}
var sql = "SELECT * FROM itemData";
if (arguments[0]) {
sql += " WHERE itemID IN (" + Zotero.join(arguments, ',') + ")";
}
var itemDataRows = Zotero.DB.query(sql);
for each(var row in itemDataRows) {
_items[row['itemID']].setField(row['fieldID'], row['value'], true);
}
return true;
}
}
@ -3915,7 +3881,7 @@ Zotero.CharacterSets = new function(){
Zotero.ItemFields = new function(){
// Private members
var _fields = [];
var _fields = {};
var _fieldsLoaded;
var _fieldFormats = [];
var _itemTypeFields = [];
@ -3944,7 +3910,12 @@ Zotero.ItemFields = new function(){
if (!_fieldsLoaded){
_loadFields();
}
return _fields['_' + field] ? _fields['_' + field]['id'] : false;
if (typeof field == 'number') {
return field;
}
return _fields[field] ? _fields[field]['id'] : false;
}
@ -3955,7 +3926,8 @@ Zotero.ItemFields = new function(){
if (!_fieldsLoaded){
_loadFields();
}
return _fields['_' + field] ? _fields['_' + field]['name'] : false;
return _fields[field] ? _fields[field]['name'] : false;
}
@ -3964,7 +3936,6 @@ Zotero.ItemFields = new function(){
// Fields in items are special cases
switch (field) {
case 'title':
case 'dateAdded':
case 'dateModified':
fieldName = field;
@ -3983,11 +3954,11 @@ Zotero.ItemFields = new function(){
_fieldCheck(fieldID, 'isValidForType');
if (!_fields['_' + fieldID]['itemTypes']){
if (!_fields[fieldID]['itemTypes']){
return false;
}
return !!_fields['_' + fieldID]['itemTypes'][itemTypeID];
return !!_fields[fieldID]['itemTypes'][itemTypeID];
}
@ -3998,7 +3969,7 @@ Zotero.ItemFields = new function(){
_fieldCheck(fieldID, 'isInteger');
var ffid = _fields['_' + fieldID]['formatID'];
var ffid = _fields[fieldID]['formatID'];
return _fieldFormats[ffid] ? _fieldFormats[ffid]['isInteger'] : false;
}
@ -4032,7 +4003,7 @@ Zotero.ItemFields = new function(){
_fieldCheck(field, arguments.callee.name);
return _fields['_' + field]['isBaseField'];
return _fields[field]['isBaseField'];
}
@ -4059,28 +4030,16 @@ Zotero.ItemFields = new function(){
}
var itemTypeID = Zotero.ItemTypes.getID(itemType);
var baseFieldID = this.getID(baseField);
if (!itemTypeID) {
throw ("Invalid item type '" + itemType + "' in ItemFields.getFieldIDFromTypeAndBase()");
}
var baseFieldID = this.getID(baseField);
if (!baseFieldID) {
throw ("Invalid field '" + baseField + '" for base field in ItemFields.getFieldIDFromTypeAndBase()');
}
if (!this.isBaseField(baseFieldID)) {
return false;
}
// If the base field is already valid for the type, just return that
if (this.isValidForType(baseFieldID, itemTypeID)) {
return baseFieldID;
}
return (_baseTypeFields[itemTypeID] &&
_baseTypeFields[itemTypeID][baseFieldID]) ?
_baseTypeFields[itemTypeID][baseFieldID] : false;
return _baseTypeFields[itemTypeID][baseFieldID];
}
@ -4173,17 +4132,39 @@ Zotero.ItemFields = new function(){
}
/*
* Build a lookup table for base field mappings
*/
function _loadBaseTypeFields() {
var sql = "SELECT itemTypeID, baseFieldID, fieldID FROM baseFieldMappings";
// Grab all fields, base field or not
var sql = "SELECT IT.itemTypeID, F.fieldID AS baseFieldID, BFM.fieldID "
+ "FROM itemTypes IT LEFT JOIN fields F "
+ "LEFT JOIN baseFieldMappings BFM"
+ " ON (IT.itemTypeID=BFM.itemTypeID AND F.fieldID=BFM.baseFieldID)";
var rows = Zotero.DB.query(sql);
var sql = "SELECT DISTINCT baseFieldID FROM baseFieldMappings";
var baseFields = Zotero.DB.columnQuery(sql);
var fields = [];
for each(var row in rows) {
if (!fields[row.itemTypeID]) {
fields[row.itemTypeID] = [];
}
if (row.fieldID) {
fields[row.itemTypeID][row.baseFieldID] = row.fieldID;
}
// If a base field and already valid for the type, just use that
else if (isBaseField(row.baseFieldID) &&
isValidForType(row.baseFieldID, row.itemTypeID)) {
fields[row.itemTypeID][row.baseFieldID] = row.baseFieldID;
}
// Set false for other fields so that we don't need to test for
// existence
else {
fields[row.itemTypeID][row.baseFieldID] = false;
}
}
_baseTypeFields = fields;
}
@ -4193,42 +4174,37 @@ Zotero.ItemFields = new function(){
* Load all fields into an internal hash array
*/
function _loadFields(){
var i,len;
var result = Zotero.DB.query('SELECT * FROM fieldFormats');
for (i=0; i<result.length; i++){
for (var i=0; i<result.length; i++){
_fieldFormats[result[i]['fieldFormatID']] = {
regex: result[i]['regex'],
isInteger: result[i]['isInteger']
};
}
result = Zotero.DB.query('SELECT * FROM fields');
if (!result.length){
throw ('No fields in database!');
}
var fields = Zotero.DB.query('SELECT * FROM fields');
var fieldItemTypes = _getFieldItemTypes();
_loadBaseTypeFields();
var sql = "SELECT DISTINCT baseFieldID FROM baseFieldMappings";
var baseFields = Zotero.DB.columnQuery(sql);
for (i=0,len=result.length; i<len; i++){
_fields['_' + result[i]['fieldID']] = {
id: result[i]['fieldID'],
name: result[i]['fieldName'],
isBaseField: (baseFields.indexOf(result[i]['fieldID']) != -1),
formatID: result[i]['fieldFormatID'],
itemTypes: fieldItemTypes[result[i]['fieldID']]
for each(var field in fields){
_fields[field['fieldID']] = {
id: field['fieldID'],
name: field['fieldName'],
isBaseField: (baseFields.indexOf(field['fieldID']) != -1),
formatID: field['fieldFormatID'],
itemTypes: fieldItemTypes[field['fieldID']]
};
// Store by name as well as id
_fields['_' + result[i]['fieldName']] = _fields['_' + result[i]['fieldID']];
_fields[field['fieldName']] = _fields[field['fieldID']];
}
_fieldsLoaded = true;
_loadBaseTypeFields();
}
}

View File

@ -587,8 +587,21 @@ Zotero.ItemTreeView.prototype.sort = function()
title: true
};
// Cache primary values while sorting, since base-field-mapped getField()
// calls are relatively expensive
var cache = [];
function columnSort(a,b) {
var cmp;
var cmp, fieldA, fieldB;
var aItemID = a.ref.getID();
if (cache[aItemID]) {
fieldA = cache[aItemID];
}
var bItemID = b.ref.getID();
if (cache[bItemID]) {
fieldB = cache[bItemID];
}
switch (columnField) {
case 'type':
@ -609,13 +622,21 @@ Zotero.ItemTreeView.prototype.sort = function()
break;
default:
var fieldA = a.getField(columnField, unformatted, true);
var fieldB = b.getField(columnField, unformatted, true);
if (fieldA == undefined) {
fieldA = a.getField(columnField, unformatted, true);
if (typeof fieldA == 'string') {
fieldA = fieldA.toLowerCase();
}
cache[aItemID] = fieldA;
}
if (fieldB == undefined) {
fieldB = b.getField(columnField, unformatted, true);
if (typeof fieldB == 'string') {
fieldB = fieldB.toLowerCase();
}
cache[bItemID] = fieldB;
}
// Display rows with empty values last
if (!emptyFirst[columnField]) {
@ -650,8 +671,8 @@ Zotero.ItemTreeView.prototype.sort = function()
}
if (columnField != 'date') {
fieldA = a.getField('date', true);
fieldB = b.getField('date', true);
fieldA = a.getField('date', true, true);
fieldB = b.getField('date', true, true);
// Display rows with empty values last
cmp = (fieldA == '' && fieldB != '') ? -1 :

View File

@ -784,6 +784,18 @@ Zotero.Schema = new function(){
Zotero.DB.query("INSERT OR IGNORE INTO itemData SELECT itemID, 52, value FROM itemData WHERE fieldID IN (14, 52) AND itemID IN (SELECT itemID FROM items WHERE itemTypeID=19) LIMIT 1");
Zotero.DB.query("DELETE FROM itemData WHERE itemID IN (SELECT itemID FROM items WHERE itemTypeID=19) AND fieldID=14");
}
if (i==21) {
Zotero.DB.query("INSERT INTO itemData SELECT itemID, 110, title FROM items WHERE title IS NOT NULL AND itemTypeID NOT IN (1,17,20,21)");
Zotero.DB.query("INSERT INTO itemData SELECT itemID, 111, title FROM items WHERE title IS NOT NULL AND itemTypeID = 17");
Zotero.DB.query("INSERT INTO itemData SELECT itemID, 112, title FROM items WHERE title IS NOT NULL AND itemTypeID = 20");
Zotero.DB.query("INSERT INTO itemData SELECT itemID, 113, title FROM items WHERE title IS NOT NULL AND itemTypeID = 21");
Zotero.DB.query("CREATE TEMPORARY TABLE itemsTemp AS SELECT itemID, itemTypeID, dateAdded, dateModified FROM items");
Zotero.DB.query("DROP TABLE items");
Zotero.DB.query("CREATE TABLE IF NOT EXISTS items (\n itemID INTEGER PRIMARY KEY,\n itemTypeID INT,\n dateAdded DATETIME DEFAULT CURRENT_TIMESTAMP,\n dateModified DATETIME DEFAULT CURRENT_TIMESTAMP\n);");
Zotero.DB.query("INSERT INTO items SELECT * FROM itemsTemp");
Zotero.DB.query("DROP TABLE itemsTemp");
}
}
_updateSchema('userdata');

1015
system.sql

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
-- 20
-- 21
-- This file creates tables containing user-specific data -- any changes
-- to existing tables made here must be mirrored in transition steps in
@ -57,7 +57,6 @@ CREATE TABLE IF NOT EXISTS userItemTypeFields (
CREATE TABLE IF NOT EXISTS items (
itemID INTEGER PRIMARY KEY,
itemTypeID INT,
title TEXT,
dateAdded DATETIME DEFAULT CURRENT_TIMESTAMP,
dateModified DATETIME DEFAULT CURRENT_TIMESTAMP
);