ydn.db
Version:
Javascript database library for IndexedDB, WebDatabase (WebSQL) and WebStorage (localStorage) storage mechanisms supporting version migration, advanced query and transaction workflow.
971 lines (813 loc) • 23.3 kB
JavaScript
// Copyright 2012 YDN Authors, Yathit. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview SQL object.
*
* Analyze SQL statement and extract execution scope.
*/
goog.provide('ydn.db.Sql_');
goog.require('goog.functions');
goog.require('ydn.db.KeyRange');
goog.require('ydn.db.schema.Database');
goog.require('ydn.error.ArgumentException');
goog.require('ydn.db.sql.req.IdbQuery');
goog.require('ydn.math.Expression');
goog.require('ydn.string');
/**
* @param {string=} sql_statement The sql statement.
* @constructor
*/
ydn.db.Sql_ = function(sql_statement) {
if (goog.isDef(sql_statement) && !goog.isString(sql_statement)) {
throw new ydn.error.ArgumentException();
}
this.sql = '';
if (goog.isDef(sql_statement)) {
this.sql = sql_statement;
//this.parseSql(sql_statement);
}
this.store_name = '';
this.wheres_ = [];
this.reduce_ = null;
this.map_ = null;
this.direction = undefined;
this.index = undefined;
this.limit_ = NaN;
this.offset_ = NaN;
};
/**
*
* @type {string}
*/
ydn.db.Sql_.prototype.store_name = '';
/**
*
* @type {string|undefined}
*/
ydn.db.Sql_.prototype.index = undefined;
/**
*
* @type {ydn.db.base.Direction|undefined}
*/
ydn.db.Sql_.prototype.direction = undefined;
/**
* @private
* @type {number}
*/
ydn.db.Sql_.prototype.limit_ = NaN;
/**
* @private
* @type {number}
*/
ydn.db.Sql_.prototype.offset_ = NaN;
/**
* @private
* @type {!Array.<!ydn.db.Where>} where clauses.
*/
ydn.db.Sql_.prototype.wheres_ = [];
/**
* @private
* @type {ydn.db.Sql_.Aggregate?} reduce function.
*/
ydn.db.Sql_.prototype.reduce_ = null;
/**
* @private
* @type {ydn.db.Sql_.Map?} map function.
*/
ydn.db.Sql_.prototype.map_ = null;
/**
* @protected
* @param {string} sql sql statement to parse.
* @return {{
* action: string,
* fields: (string|!Array.<string>|undefined),
* store_name: string,
* wheres: !Array.<string>
* }} functional equivalent of SQL.
* @throws {ydn.error.ArgumentException}
*/
ydn.db.Sql_.prototype.parseSql = function(sql) {
var from_parts = sql.split(/\sFROM\s/i);
if (from_parts.length != 2) {
throw new ydn.error.ArgumentException('FROM required.');
}
// Parse Pre-FROM
var pre_from_parts = from_parts[0].match(
/\s*?(SELECT|COUNT|MAX|AVG|MIN|CONCAT)\s*(.*)/i);
if (pre_from_parts.length != 3) {
throw new ydn.error.ArgumentException('Unable to parse: ' + sql);
}
var action = pre_from_parts[1].toUpperCase();
var action_arg = pre_from_parts[2].trim();
var fields = undefined;
if (action_arg.length > 0 && action_arg != '*') {
if (action_arg[0] == '(') {
action_arg = action_arg.substring(1);
}
if (action_arg[action_arg.length - 2] == ')') {
action_arg = action_arg.substring(0, action_arg.length - 2);
}
if (action_arg.indexOf(',') > 0) {
fields = ydn.string.split_comma_seperated(action_arg);
fields = goog.array.map(fields, function(x) {
return goog.string.stripQuotes(x, '"');
});
} else {
fields = action_arg;
}
}
// Parse FROM
var parts = from_parts[1].trim().match(/"(.+)"\s*(.*)/);
if (!parts) {
throw new ydn.error.ArgumentException('store name required.');
}
var store_name = parts[1];
var wheres = [];
if (parts.length > 2) {
wheres.push(parts[2]);
}
return {
action: action,
fields: fields,
store_name: store_name,
wheres: wheres
};
};
/**
* @private
* @type {string} sql statement.
*/
ydn.db.Sql_.prototype.sql_ = '';
/**
* @inheritDoc
*/
ydn.db.Sql_.prototype.toJSON = function() {
return {
'sql': this.sql_
};
};
/**
*
* @param {string} store_name store name to query from.
* @return {ydn.db.Sql_} this query for chaining.
*/
ydn.db.Sql_.prototype.from = function(store_name) {
this.store_name = store_name;
return this;
};
/**
*
* @param {boolean} value if <code>true</code>, the cursor should not yield
* records with the same key.
* @return {ydn.db.Sql_} this query for chaining.
*/
ydn.db.Sql_.prototype.unique = function(value) {
if (this.direction == ydn.db.base.Direction.NEXT ||
this.direction == ydn.db.base.Direction.NEXT_UNIQUE) {
this.direction = !!value ? ydn.db.base.Direction.NEXT_UNIQUE :
ydn.db.base.Direction.NEXT;
} else {
this.direction = !!value ? ydn.db.base.Direction.PREV_UNIQUE :
ydn.db.base.Direction.PREV;
}
return this;
};
/**
*
* @param {boolean} value if <code>true</code>, the cursor should yield
* monotonically decreasing order of keys..
* @return {ydn.db.Sql_} this query for chaining.
*/
ydn.db.Sql_.prototype.reverse = function(value) {
if (this.direction == ydn.db.base.Direction.NEXT_UNIQUE ||
this.direction == ydn.db.base.Direction.PREV_UNIQUE) {
this.direction = !!value ? ydn.db.base.Direction.PREV_UNIQUE :
ydn.db.base.Direction.NEXT_UNIQUE;
} else {
this.direction = !!value ? ydn.db.base.Direction.PREV :
ydn.db.base.Direction.NEXT;
}
return this;
};
/**
*
* @param {string} index name of index to order.
*/
ydn.db.Sql_.prototype.order = function(index) {
this.index = index;
};
/**
* Convenient method for SQL <code>WHERE</code> predicate.
* @param {string} field index field name to query from.
* @param {string} op where operator.
* @param {string} value rvalue to compare.
* @param {string=} op2 secound operator.
* @param {string=} value2 second rvalue to compare.
* @return {!ydn.db.Sql_} The query.
*/
ydn.db.Sql_.prototype.where = function(field, op, value, op2, value2) {
var already = goog.array.some(this.wheres_, function(x) {
return x.field === field;
});
if (already) {
throw new ydn.error.ArgumentException(field);
}
this.wheres_.push(new ydn.db.Where(field, op, value, op2, value2));
return this;
};
/**
* @param {ydn.math.Expression} expression expression.
* @param {Array.<string>|string} fields projection fields.
* @struct
* @constructor
*/
ydn.db.Sql_.Map = function(expression, fields) {
/**
* @final
* @type {ydn.math.Expression}
*/
this.expression = expression;
/**
* @final
* @type {Array.<string>}
*/
this.fields = goog.isArray(fields) ? fields : null;
/**
* @final
* @type {string|undefined}
*/
this.field = goog.isString(fields) ? fields : undefined;
this.type = goog.isDefAndNotNull(expression) ?
ydn.db.Sql_.MapType.EXPRESSION :
goog.isString(this.field) ? ydn.db.Sql_.MapType.SELECT :
ydn.db.Sql_.MapType.SELECT_MANY;
};
/**
* @enum {string}
*/
ydn.db.Sql_.MapType = {
SELECT_MANY: 'sl',
SELECT: 's1',
EXPRESSION: 'ex'
};
/**
* @enum {string}
*/
ydn.db.Sql_.AggregateType = {
COUNT: 'ct',
SUM: 'sm',
AVERAGE: 'av',
MAX: 'mx',
MIN: 'mn',
SELECT: 'sl',
CONCAT: 'cc',
EXPRESSION: 'ex'
};
/**
* @typedef {{
* type: ydn.db.Sql_.AggregateType,
* field: (string|undefined)
* }}
*/
ydn.db.Sql_.Aggregate;
//
///**
// * Convenient method for SQL <code>COUNT</code> method.
// * @return {!ydn.db.Sql_} The query.
// */
//ydn.db.Sql_.prototype.count = function() {
//
// if (this.reduce_) {
// throw new ydn.error.ConstraintError('Aggregate method already defined.');
// }
// this.reduce_ = {type: ydn.db.Sql_.AggregateType.COUNT, field: undefined};
// return this;
//
//};
/**
* Return reduce iteration function for SUM
* @param {string=} field field name.
* @return {Function} count.
*/
ydn.db.Sql_.reduceCount = function(field) {
return function(curr, prev) {
return prev + 1;
};
};
//
//
///**
// * Convenient method for SQL <code>SUM</code> method.
// * @param {string} field name.
// * @return {!ydn.db.Sql_} The query for chaining.
// */
//ydn.db.Sql_.prototype.sum = function(field) {
//
// if (this.reduce_) {
// throw new ydn.error.ConstraintError('Aggregate method already defined.');
// }
// this.reduce_ = {
// type: ydn.db.Sql_.AggregateType.SUM,
// field: field
// };
// return this;
//
//
//};
/**
* Return reduce iteration function for SUM
* @param {string} field field name.
* @return {Function} sum.
*/
ydn.db.Sql_.reduceSum = function(field) {
return function(curr, prev, i) {
return prev + curr[field];
};
};
/**
* Return reduce iteration function for MIN
* @param {string} field name.
* @return {Function} min.
*/
ydn.db.Sql_.reduceMin = function(field) {
return function(curr, prev, i) {
var x = curr[field];
if (!goog.isDef(prev)) {
return x;
}
return prev < x ? prev : x;
};
};
/**
* Return reduce iteration function for MAX
* @param {string} field name.
* @return {Function} max.
*/
ydn.db.Sql_.reduceMax = function(field) {
return function(curr, prev, i) {
var x = curr[field];
if (!goog.isDef(prev)) {
return x;
}
return prev > x ? prev : x;
};
};
//
//
///**
// * Convenient method for SQL <code>AVERAGE</code> method.
// * @param {string} field name.
// * @return {!ydn.db.Sql_} The query for chaining.
// */
//ydn.db.Sql_.prototype.average = function(field) {
//
// if (this.reduce_) {
// throw new ydn.error.ConstraintError('Aggregate method already defined.');
// }
// this.reduce_ = {
// type: ydn.db.Sql_.AggregateType.AVERAGE,
// field: field
// };
// return this;
//
//
//};
/**
* Return reduce iteration function for AVERAGE
* @param {string} field name.
* @return {Function} average.
*/
ydn.db.Sql_.reduceAverage = function(field) {
return function(curr, prev, i) {
if (!goog.isDef(prev)) {
prev = 0;
}
return (prev * i + curr[field]) / (i + 1);
};
};
/**
*
* @param {string|ydn.math.Expression} method selection method.
* @param {string=} fields field names to select.
* @return {!ydn.db.Sql_} The query for chaining.
*/
ydn.db.Sql_.prototype.aggregate = function(method, fields) {
if (this.reduce_) {
throw new ydn.error.ArgumentException('too many reduce.');
}
if (method instanceof ydn.math.Expression) {
var exp = method;
this.reduce_ = /** @type {ydn.db.Sql_.Aggregate} */ ({
type:ydn.db.Sql_.AggregateType.EXPRESSION,
fields:''
}); // why casting ??
return this;
} else if (goog.isString(method)) {
method = method.toLocaleLowerCase();
} else {
throw new ydn.error.ArgumentException();
}
if (method === 'avg') {
if (!goog.isString(fields)) {
throw new ydn.error.ArgumentException('AVG');
}
this.reduce_ = {
type: ydn.db.Sql_.AggregateType.AVERAGE,
field: fields
};
} else if (method === 'min') {
if (!goog.isString(fields)) {
throw new ydn.error.ArgumentException('MIN');
}
this.reduce_ = {
type: ydn.db.Sql_.AggregateType.MIN,
field: fields
};
} else if (method === 'max') {
if (!goog.isString(fields)) {
throw new ydn.error.ArgumentException('MAX');
}
this.reduce_ = {
type: ydn.db.Sql_.AggregateType.MAX,
expr: null,
field: fields
};
} else if (method === 'sum') {
if (!goog.isString(fields)) {
throw new ydn.error.ArgumentException('SUM');
}
this.reduce_ = {
type: ydn.db.Sql_.AggregateType.SUM,
field: fields
};
} else if (method === 'count') {
if (goog.isString(fields)) {
this.reduce_ = {type: ydn.db.Sql_.AggregateType.COUNT, field: fields};
} else if (goog.isDef(fields)) {
throw new ydn.error.ArgumentException('COUNT');
} else {
this.reduce_ = {type: ydn.db.Sql_.AggregateType.COUNT, field: undefined};
}
} else {
throw new ydn.error.ArgumentException('Unknown reduce method: ' +
method);
}
return this;
};
/**
*
* @param {(ydn.math.Expression|string|!Array.<string>)=} exp_or_fields field names to select.
* @return {!ydn.db.Sql_} The query for chaining.
*/
ydn.db.Sql_.prototype.project = function(exp_or_fields) {
if (this.map_) {
throw new ydn.error.ConstraintError('too many call.');
}
var method = '';
if (exp_or_fields instanceof ydn.math.Expression) {
this.map_ = new ydn.db.Sql_.Map(exp_or_fields, null);
} else {
if (goog.isString(exp_or_fields) || goog.isArray(exp_or_fields)) {
this.map_ = new ydn.db.Sql_.Map(null, exp_or_fields);
} else {
throw new ydn.error.ArgumentException();
}
}
return this;
};
/**
*
* @param {string} fields field names.
* @return {Function} select projection function.
*/
ydn.db.Sql_.mapSelect = function (fields) {
return function (data) {
return data[fields];
};
};
/**
*
* @param {!Array.<string>} fields field names.
* @return {Function} select projection function.
*/
ydn.db.Sql_.mapSelectMany = function (fields) {
return function (data) {
var selected_data = {};
for (var i = 0; i < fields.length; i++) {
selected_data[fields[i]] = data[fields[i]];
}
return selected_data;
};
};
/**
*
* @return {!Array.<string>} store name.
*/
ydn.db.Sql_.prototype.stores = function() {
return [this.store_name];
};
/**
*
* @return {ydn.db.base.TransactionMode} store name.
*/
ydn.db.Sql_.prototype.mode = function() {
return ydn.db.base.TransactionMode.READ_ONLY;
};
/**
*
* @return {string} get the first store name.
*/
ydn.db.Sql_.prototype.getStoreName = function() {
return this.store_name_[0];
};
/**
* Parse SQL statement and convert to cursor object for IndexedDB execution.
* @see #toSqlCursor
* @param {ydn.db.schema.Database} schema schema.
* @return {!ydn.db.sql.req.IdbQuery} cursor.
*/
ydn.db.Sql_.prototype.toIdbQuery = function(schema) {
if (this.store_name.length == 0) {
throw new ydn.error.InvalidOperationException('store name not set.');
}
var store = schema.getStore(this.store_name);
if (!store) {
throw new ydn.error.InvalidOperationException('store: ' + this.store_name +
' not found.');
}
var key_range;
var index = this.index;
var direction = this.direction;
// sniff index field
if (!goog.isDef(this.index)) {
for (var i = 0; i < this.wheres_.length; i++) {
/**
* @type {ydn.db.Where}
*/
var where = this.wheres_[i];
if (store.hasIndex(where.field)) {
index = where.field;
key_range = where;
direction = direction || ydn.db.base.Direction.NEXT;
this.wheres_.splice(i, 1);
break;
}
}
}
var cursor = new ydn.db.sql.req.IdbQuery(this.store_name, index, key_range);
// then, process where clauses
for (var i = 0; i < this.wheres_.length; i++) {
cursor.processWhereAsFilter(this.wheres_[i]);
}
if (this.map_) {
if (this.map_.type == ydn.db.Sql_.MapType.SELECT &&
goog.isString(this.map_.field)) {
cursor.map = ydn.db.Sql_.mapSelect(this.map_.field);
} else if (this.map_.type == ydn.db.Sql_.MapType.SELECT_MANY &&
goog.isArray(this.map_.fields)) {
cursor.map = ydn.db.Sql_.mapSelectMany(this.map_.fields);
} else {
throw new ydn.db.SqlParseError('map');
}
}
if (this.reduce_) {
if (this.reduce_.type == ydn.db.Sql_.AggregateType.SUM) {
if (goog.isString(this.reduce_.field)) {
cursor.initial = goog.functions.constant(0);
cursor.reduce = ydn.db.Sql_.reduceSum(this.reduce_.field);
} else {
throw new ydn.db.SqlParseError('SUM: ' + this.sql_);
}
} else if (this.reduce_.type == ydn.db.Sql_.AggregateType.MIN) {
if (goog.isString(this.reduce_.field)) {
cursor.reduce = ydn.db.Sql_.reduceMin(this.reduce_.field);
} else {
throw new ydn.db.SqlParseError('MIN: ' + this.sql_);
}
} else if (this.reduce_.type == ydn.db.Sql_.AggregateType.MAX) {
if (goog.isString(this.reduce_.field)) {
cursor.reduce = ydn.db.Sql_.reduceMax(this.reduce_.field);
} else {
throw new ydn.db.SqlParseError('MAX: ' + this.sql_);
}
} else if (this.reduce_.type == ydn.db.Sql_.AggregateType.AVERAGE) {
if (goog.isString(this.reduce_.field)) {
cursor.reduce = ydn.db.Sql_.reduceAverage(this.reduce_.field);
} else {
throw new ydn.db.SqlParseError('AVERAGE: ' + this.sql_);
}
} else if (this.reduce_.type == ydn.db.Sql_.AggregateType.COUNT) {
cursor.initial = goog.functions.constant(0);
cursor.reduce = ydn.db.Sql_.reduceCount(this.reduce_.field);
} else {
throw new ydn.db.SqlParseError(this.sql_);
}
}
//goog.global.console.log([this, cursor]);
return cursor;
};
/**
*
* @param {number} value limit value.
*/
ydn.db.Sql_.prototype.limit = function(value) {
if (goog.isNumber(value) && value > 0) {
this.limit_ = value;
} else {
throw new ydn.error.ArgumentException();
}
};
/**
*
* @param {number} value offset value.
*/
ydn.db.Sql_.prototype.offset = function(value) {
if (goog.isNumber(value) && value >= 0) {
this.offset_ = value;
} else {
throw new ydn.error.ArgumentException();
}
};
/**
* Convert this query into iterable cursor object for WebSQL execution.
* @see #toCursor
* @param {!ydn.db.schema.Database} schema schema.
* @return {!ydn.db.Iterator} cursor.
*/
ydn.db.Sql_.prototype.toSqlQuery = function(schema) {
if (this.sql_.length > 0) {
throw new ydn.error.NotImplementedException('SQL parser not implement');
}
if (this.store_name.length == 0) {
throw new ydn.error.InvalidOperationException('store name not set.');
}
var store = schema.getStore(this.store_name);
if (!store) {
throw new ydn.error.InvalidOperationException('store: ' + this.store_name +
' not found.');
}
var cursor = new ydn.db.sql.req.SqlQuery(this.store_name);
var from = 'FROM ' + goog.string.quote(this.store_name);
var select = '';
var distinct = this.direction == ydn.db.base.Direction.PREV_UNIQUE ||
this.direction == ydn.db.base.Direction.NEXT_UNIQUE;
var fields_selected = false;
if (goog.isDefAndNotNull(this.map_)) {
if (this.map_.type == ydn.db.Sql_.MapType.SELECT_MANY ||
this.map_.type == ydn.db.Sql_.MapType.SELECT) {
var fs = this.map_.type == ydn.db.Sql_.MapType.SELECT ?
[this.map_.field] : this.map_.fields;
goog.asserts.assertArray(fs);
var fields = goog.array.map(fs, function (x) {
return goog.string.quote(x);
});
select += 'SELECT (' + fields.join(', ') + ')';
fields_selected = true;
// parse row and then select the fields.
cursor.parseRow = ydn.db.sql.req.SqlQuery.parseRowIdentity;
if (this.map_.type == ydn.db.Sql_.MapType.SELECT &&
goog.isString(this.map_.field)) {
cursor.map = ydn.db.Sql_.mapSelect(this.map_.field);
} else if (this.map_.type == ydn.db.Sql_.MapType.SELECT_MANY &&
goog.isArray(this.map_.fields)) {
cursor.map = ydn.db.Sql_.mapSelectMany(this.map_.fields);
}
} else {
throw new ydn.error.NotImplementedException('map in ' + this.sql_);
}
}
if (goog.isDefAndNotNull(this.reduce_)) {
if (this.reduce_.type == ydn.db.Sql_.AggregateType.COUNT) {
select += 'SELECT COUNT (';
select += distinct ? 'DISTINCT ' : '';
if (goog.isString(this.reduce_.field)) {
select += goog.string.quote(this.reduce_.field);
} else {
select += '*';
}
select += ')';
fields_selected = true;
// parse row and then select the fields.
cursor.parseRow = ydn.db.sql.req.SqlQuery.parseRowIdentity;
cursor.map = ydn.object.takeFirst;
cursor.finalize = ydn.db.Sql_.finalizeTakeFirst;
} else if (this.reduce_.type == ydn.db.Sql_.AggregateType.SUM) {
select += 'SELECT SUM (';
select += distinct ? 'DISTINCT ' : '';
if (goog.isString(this.reduce_.field)) {
select += goog.string.quote(this.reduce_.field);
} else {
select += '*';
}
select += ')';
fields_selected = true;
// parse row and then select the fields.
cursor.parseRow = ydn.db.sql.req.SqlQuery.parseRowIdentity;
cursor.map = ydn.object.takeFirst;
cursor.finalize = ydn.db.Sql_.finalizeTakeFirst;
} else if (this.reduce_.type == ydn.db.Sql_.AggregateType.AVERAGE) {
select += 'SELECT AVG (';
select += distinct ? 'DISTINCT ' : '';
if (goog.isString(this.reduce_.field)) {
select += goog.string.quote(this.reduce_.field);
} else {
select += '*';
}
select += ')';
fields_selected = true;
// parse row and then select the fields.
cursor.parseRow = ydn.db.sql.req.SqlQuery.parseRowIdentity;
cursor.map = ydn.object.takeFirst;
cursor.finalize = ydn.db.Sql_.finalizeTakeFirst;
} else {
throw new ydn.db.SqlParseError(this.reduce_.type + ' in ' + this.sql_);
}
}
if (select.length == 0) {
select += 'SELECT *' + (distinct ? ' DISTINCT' : '');
}
var where = '';
for (var i = 0; i < this.wheres_.length; i++) {
if (store.hasIndex(this.wheres_[i].field)) {
if (where.length > 0) {
where += ' AND ';
} else {
where += 'WHERE ';
}
var where_clause = this.wheres_[i].toWhereClause();
where += where_clause.sql;
if (where_clause.params.length > 0) {
cursor.params = cursor.params.concat(where_clause.params);
}
} else {
cursor.processWhereAsFilter(this.wheres_[i]);
}
}
var field_name = goog.isDefAndNotNull(this.index) ?
goog.string.quote(this.index) : goog.isDefAndNotNull(store.keyPath) ?
goog.string.quote(store.keyPath) : ydn.db.base.SQLITE_SPECIAL_COLUNM_NAME;
var order = 'ORDER BY ' + field_name;
if (this.direction == ydn.db.base.Direction.PREV ||
this.direction == ydn.db.base.Direction.PREV_UNIQUE) {
order += ' DESC';
} else {
order += ' ASC';
}
var range = '';
if (!isNaN(this.limit_)) {
range += ' LIMIT ' + this.limit_;
}
if (!isNaN(this.offset_)) {
range += ' OFFSET ' + this.offset_;
}
cursor.sql = select + ' ' + from + ' ' + where + ' ' + order + ' ' + range;
return cursor;
};
/**
* @override
*/
ydn.db.Sql_.prototype.toString = function() {
if (goog.DEBUG) {
return 'query:' + this.sql_;
} else {
return goog.base(this, 'toString');
}
};
/**
* Parse resulting object of a row
* @final
* @param {ydn.db.schema.Store} table table of concern.
* @param {!Object} row row.
* @return {*} the first field of object in row value.
*/
ydn.db.Sql_.parseRowTakeFirst = function(table, row) {
for (var key in row) {
if (row.hasOwnProperty(key)) {
return row[key];
}
}
return undefined;
};
/**
*
* @param {*} arr array.
* @return {*} get the first element of an array.
*/
ydn.db.Sql_.finalizeTakeFirst = function(arr) {
if (goog.isArray(arr)) {
return arr[0];
} else {
return undefined;
}
};