apiCall(params, function(_response) {
				if (_response.success) {
					var data = JSON.parse(_response.responseText);
					if(DEBUG){ 
						Ti.API.info("[SQL REST API] server response: ");
						Ti.API.debug(data) 
					}
					var currentModels = sqlCurrentModels();
					if (_.indexOf(currentModels, Number(data[model.idAttribute])) != -1) {
						resp = updateSQL(data); //item exists - update it
					} else {
						resp = createSQL(data); //write remote data to local sql
					}
					_.isFunction(params.success) && params.success(resp);
				} else {
					//error or offline - use local data
					var currentModels = sqlCurrentModels();
					if (_.indexOf(currentModels, Number(model.id)) != -1) {
						resp = updateSQL(); //item exists - update it
					} else {
						resp = createSQL(); //write remote data to local sql
					}
					if(_.isUndefined(_response.offline)){
						//error
						_.isFunction(params.error) && params.error(resp);	
					} else {
						//offline - still a data success
						_.isFunction(params.success) && params.success(resp);
					}
				}
			});
Example #2
0
	/////////////////////////////////////////////
	//SQL INTERFACE
	/////////////////////////////////////////////
	function saveData(data) {
		if (!data && !isCollection) {
			data = model.toJSON();
		}
		if (!data) {
			// its empty
			return;
		}
		if (!_.isArray(data)) {// its a model
			if (!_.isUndefined(data["is_deleted"])) {
				//delete item
				deleteSQL(data[model.idAttribute]);
			} else if (sqlFindItem(data[model.idAttribute]).length == 1) {
				//item exists - update it
				return updateSQL(data);
			} else {
				//write data to local sql
				return createSQL(data);
			}
		} else {//its an array of models
			var currentModels = sqlCurrentModels();
			for (var i in data) {
				if (!_.isUndefined(data[i]["is_deleted"])) {
					//delete item
					deleteSQL(data[i][model.idAttribute]);
				} else if (_.indexOf(currentModels, data[i][model.idAttribute]) != -1) {
					//item exists - update it
					updateSQL(data[i]);
				} else {
					//write data to local sql
					createSQL(data[i]);
				}
			}
		}
	}
Example #3
0
  function updateSQL(data, queryList) {
    var attrObj = {};

    logger(DEBUG, "updateSQL data: ", data);

    if (data) {
      attrObj = data;
    } else {
      if (!isCollection) {
        attrObj = model.toJSON();
      } else {
        Ti.API.error("Its a collection - error!");
      }
    }

    // Create arrays for insert query
    var names = [],
      values = [],
      q = [];
    for (var k in columns) {
      if (!_.isUndefined(attrObj[k])) {//only update those who are in the data
        names.push(k + '=?');
        if (_.isObject(attrObj[k])) {
          values.push(JSON.stringify(attrObj[k]));
        } else {
          values.push(attrObj[k]);
        }
        q.push('?');
      }
    }

    if (params.lastModifiedColumn && _.isUndefined(params.disableLastModified)) {
      values[_.indexOf(names, params.lastModifiedColumn + "=?")] = params.lastModifiedDateFormat ? moment().format(params.lastModifiedDateFormat) : moment().lang('en').zone('GMT').format('YYYY-MM-DD HH:mm:ss ZZ');
    }

    // compose the update query
    var sql = 'UPDATE ' + table + ' SET ' + names.join(',') + ' WHERE ' + model.idAttribute + '=?';
    values.push(attrObj[model.idAttribute]);

    logger(DEBUG, "updateSQL sql query: " + sql);
    logger(DEBUG, "updateSQL values: ", values);

    if (queryList) {
      queryList.push({
        "sql" : sql,
        "values" : values
      });
      return queryList;
    } else {
      // execute the update
      db = Ti.Database.open(dbName);
      db.execute(sql, values);
      db.close();
    }

    return attrObj;
  }
Example #4
0
 function saveData(data) {
     data || isCollection || (data = model.toJSON());
     if (!data) return;
     if (_.isArray(data)) {
         var currentModels = sqlCurrentModels();
         for (var i in data) _.isUndefined(data[i]["is_deleted"]) ? -1 != _.indexOf(currentModels, data[i][model.idAttribute]) ? updateSQL(data[i]) : createSQL(data[i]) : deleteSQL(data[i][model.idAttribute]);
     } else {
         if (_.isUndefined(data["is_deleted"])) return 1 == sqlFindItem(data[model.idAttribute]).length ? updateSQL(data) : createSQL(data);
         deleteSQL(data[model.idAttribute]);
     }
 }
Example #5
0
function encodeData(obj, url) {
	var str = [];
	for (var p in obj) {
		str.push(Ti.Network.encodeURIComponent(p) + "=" + Ti.Network.encodeURIComponent(obj[p]));
	}

	if (_.indexOf(url, "?") == -1) {
		return url + "?" + str.join("&");
	} else {
		return url + "&" + str.join("&");
	}
}
function encodeData(obj, url) {
	var _serialize = function(obj, prefix) {
		var str = [];
		for (var p in obj) {
			if (obj.hasOwnProperty(p)) {
				var k = prefix ? prefix + "[" + p + "]" : p, v = obj[p];
				str.push( typeof v === "object" ? _serialize(v, k) : Ti.Network.encodeURIComponent(k) + "=" + Ti.Network.encodeURIComponent(v));
			}
		}
		return str.join("&");
	};

	return url + (_.indexOf(url, "?") === -1 ? "?" : "&") + _serialize(obj);
}
			apiCall(params, function(_response) {
				if (_response.success) {
					var data = JSON.parse(_response.responseText);
					if(DEBUG){ 
						Ti.API.info("[SQL REST API] server response: ");
						Ti.API.debug(data) 
					}
					if(_.isUndefined(params.localOnly)){ //we dont want to manipulate the data on localOnly requests
						if(!_.isArray(data)){ //its a model				
							if(!_.isUndefined(data["is_deleted"])){ //delete item
								deleteSQL(dat[model.idAttribute]);
							} else if(sqlFindItem(data[model.idAttribute]).length == 1){
								updateSQL(data); //item exists - update it
							} else {
								createSQL(data); //write remote data to local sql
							}				
						} else { //its an array of models
							var currentModels = sqlCurrentModels();
							for (var i in data) {
								if(!_.isUndefined(data["is_deleted"])){ //delete item
									deleteSQL(data[i][model.idAttribute]);
								} else if (_.indexOf(currentModels, Number(data[i][model.idAttribute])) != -1) {
									updateSQL(data[i]); //item exists - update it
								} else {
									createSQL(data[i]); //write remote data to local sql
								}
							}
						}
					}
					resp = readSQL();
					_.isFunction(params.success) && params.success(resp);
					model.trigger("fetch");
				} else {
					//error or offline - read local data
					resp = readSQL();
					if(_.isUndefined(_response.offline)){
						//error
						_.isFunction(params.error) && params.error(resp);	
					} else {
						//offline - still a data success
						_.isFunction(params.success) && params.success(resp);
						model.trigger("fetch");
					}
				}
			});
Example #8
0
 function createSQL(data) {
     var attrObj = {};
     if (DEBUG) {
         Ti.API.debug("[SQL REST API] createSQL data:");
         Ti.API.debug(data);
     }
     data ? attrObj = data : isCollection ? Ti.API.error("[SQL REST API] Its a collection - error !") : attrObj = model.toJSON();
     if (!attrObj[model.idAttribute]) if (model.idAttribute === ALLOY_ID_DEFAULT) {
         attrObj.id = guid();
         attrObj[model.idAttribute] = attrObj.id;
     } else attrObj[model.idAttribute] = null;
     if (useStrictValidation) for (var c in columns) {
         if (c == model.idAttribute) continue;
         if (!_.contains(_.keys(attrObj), c)) {
             Ti.API.error("[SQL REST API] ITEM NOT VALID - REASON: " + c + " is not present");
             return;
         }
     }
     var names = [], values = [], q = [];
     for (var k in columns) {
         names.push(k);
         _.isObject(attrObj[k]) ? values.push(JSON.stringify(attrObj[k])) : values.push(attrObj[k]);
         q.push("?");
     }
     lastModifiedColumn && _.isUndefined(params.disableLastModified) && (values[_.indexOf(names, lastModifiedColumn)] = lastModifiedDateFormat ? moment().format(lastModifiedDateFormat) : moment().format("YYYY-MM-DD HH:mm:ss"));
     var sqlInsert = "INSERT INTO " + table + " (" + names.join(",") + ") VALUES (" + q.join(",") + ");";
     db = Ti.Database.open(dbName);
     db.execute("BEGIN;");
     db.execute(sqlInsert, values);
     if (null === model.id) {
         var sqlId = "SELECT last_insert_rowid();";
         var rs = db.execute(sqlId);
         if (rs.isValidRow()) {
             model.id = rs.field(0);
             attrObj[model.idAttribute] = model.id;
         } else Ti.API.warn("Unable to get ID from database for model: " + model.toJSON());
     }
     db.execute("COMMIT;");
     db.close();
     return attrObj;
 }
Example #9
0
      function iteration(data, i, queryList) {
        i || (i = 0);
        queryList = queryList || [];

        if (!_.isUndefined(data[i][model.deletedAttribute]) && data[i][model.deletedAttribute] == true) {
          //delete item
          queryList = deleteSQL(data[i][model.idAttribute], queryList);
        } else if (_.indexOf(currentModels, data[i][model.idAttribute]) != -1) {
          //item exists - update it
          queryList = updateSQL(data[i], queryList);
        } else {
          //write data to local sql
          queryList = createSQL(data[i], queryList);
        }

        if(++i < data.length) {
          _.defer(iteration, data, i, queryList);
        } else {
          _.defer(function() {
            if (queryList && queryList.length) {
              db = Ti.Database.open(dbName);
              db.execute('BEGIN;');
              _.each(queryList, function(query) {
                try {
                  db.execute(query.sql, query.values);
                } catch (e) {
                  //
                }
              });
              db.execute('COMMIT;');
              db.close();
            }

            callback && callback();
          });
        }
      }
Example #10
0
	function createSQL(data) {
		var attrObj = {};

		if (DEBUG) {
			Ti.API.debug("[SQL REST API] createSQL data:");
			Ti.API.debug(data);
		}

		if (data) {
			attrObj = data;
		} else {
			if (!isCollection) {
				attrObj = model.toJSON();
			} else {
				Ti.API.error("[SQL REST API] Its a collection - error !");
			}
		}

		if (!attrObj[model.idAttribute]) {
			if (model.idAttribute === ALLOY_ID_DEFAULT) {
				// alloy-created GUID field
				attrObj.id = util.guid();
				attrObj[model.idAttribute] = attrObj.id;
			} else {
				// idAttribute not assigned by alloy. Leave it empty and
				// allow sqlite to process as null, which is the
				// expected value for an AUTOINCREMENT field.
				attrObj[model.idAttribute] = null;
			}
		}

		//validate the item
		if (useStrictValidation) {
			for (var c in columns) {
				if (c == model.idAttribute) {
					continue;
				}
				if (!_.contains(_.keys(attrObj), c)) {
					Ti.API.error("[SQL REST API] ITEM NOT VALID - REASON: " + c + " is not present");
					return;
				}
			}
		}

		// Create arrays for insert query
		var names = [], values = [], q = [];
		for (var k in columns) {
			names.push(k);
			if (_.isObject(attrObj[k])) {
				values.push(JSON.stringify(attrObj[k]));
			} else {
				values.push(attrObj[k]);
			}
			q.push('?');
		}
		// Last Modified logic
		// 
		if (lastModifiedColumn && _.isUndefined(params.disableLastModified)) {
			values[_.indexOf(names, lastModifiedColumn)] = lastModifiedDateFormat ? moment().format(lastModifiedDateFormat) : moment().format('YYYY-MM-DD HH:mm:ss');
		}

		// Assemble create query
		var sqlInsert = "INSERT INTO " + table + " (" + names.join(",") + ") VALUES (" + q.join(",") + ");";

		// execute the query and return the response
		db = Ti.Database.open(dbName);
		db.execute('BEGIN;');
		db.execute(sqlInsert, values);

		// get the last inserted id
		if (model.id === null) {
			var sqlId = "SELECT last_insert_rowid();";
			var rs = db.execute(sqlId);
			if (rs.isValidRow()) {
				model.id = rs.field(0);
				attrObj[model.idAttribute] = model.id;
			} else {
				Ti.API.warn('Unable to get ID from database for model: ' + model.toJSON());
			}
		}

		db.execute('COMMIT;');
		db.close();

		return attrObj;
	}
Example #11
0
function Sync(method, model, opts) {
	var table = model.config.adapter.collection_name, columns = model.config.columns, dbName = model.config.adapter.db_name || ALLOY_DB_DEFAULT, resp = null, db;
	model.idAttribute = model.config.adapter.idAttribute;
	
	// fix for collection
	var DEBUG = model.config.debug;
	
	// last modified 
	var lastModifiedColumn = model.config.adapter.lastModifiedColumn;
	var addModifedToUrl = model.config.adapter.addModifedToUrl;
	var lastModifiedDateFormat = model.config.adapter.lastModifiedDateFormat;
	
	var parentNode = model.config.parentNode;
	var useStrictValidation = model.config.useStrictValidation;
	var initFetchWithLocalData = model.config.initFetchWithLocalData;
	var deleteAllOnFetch = model.config.deleteAllOnFetch;
	
	var isCollection = ( model instanceof Backbone.Collection) ? true : false;
	var returnErrorResponse = model.config.returnErrorResponse;
	
	
	var singleModelRequest = null;
	if (lastModifiedColumn) {
		if (opts.sql && opts.sql.where) {
			singleModelRequest = opts.sql.where[model.idAttribute];
		}
		if (!singleModelRequest && opts.data && opts.data[model.idAttribute]) {
			singleModelRequest = opts.data[model.idAttribute];
		}
	}

	//REST API
	var methodMap = {
		'create' : 'POST',
		'read' : 'GET',
		'update' : 'PUT',
		'delete' : 'DELETE'
	};

	var type = methodMap[method];
	var params = _.extend({}, opts);
	params.type = type;

	//set default headers
	params.headers = params.headers || {};

	// Send our own custom headers
	if (model.config.hasOwnProperty("headers")) {
		for (header in model.config.headers) {
			params.headers[header] = model.config.headers[header];
		}
	}

	// We need to ensure that we have a base url.
	if (!params.url) {
		params.url = (model.config.URL || model.url());
		if (!params.url) {
			Ti.API.error("[SQL REST API] ERROR: NO BASE URL");
			return;
		}
	}
	
	if (lastModifiedColumn && _.isUndefined(params.disableLastModified)) {
		//send last modified model datestamp to the remote server
		var lastModifiedValue = "";
		try {
			lastModifiedValue = sqlLastModifiedItem();
		} catch(e) {
			if (DEBUG) {
				Ti.API.debug("[SQL REST API] LASTMOD SQL FAILED: ");
			}
		}
		params.headers['Last-Modified'] = lastModifiedValue;
	}

	// For older servers, emulate JSON by encoding the request into an HTML-form.
	if (Alloy.Backbone.emulateJSON) {
		params.contentType = 'application/x-www-form-urlencoded';
		params.processData = true;
		params.data = params.data ? {
			model : params.data
		} : {};
	}

	// For older servers, emulate HTTP by mimicking the HTTP method with `_method`
	// And an `X-HTTP-Method-Override` header.
	if (Alloy.Backbone.emulateHTTP) {
		if (type === 'PUT' || type === 'DELETE') {
			if (Alloy.Backbone.emulateJSON)
				params.data._method = type;
			params.type = 'POST';
			params.beforeSend = function(xhr) {
				params.headers['X-HTTP-Method-Override'] = type;
			};
		}
	}

	//json data transfers
	params.headers['Content-Type'] = 'application/json';

	if (DEBUG) {
		Ti.API.debug("[SQL REST API] REST METHOD: " + method);
	}

	switch (method) {
		case 'create':
			// convert to string for API call
			params.data = JSON.stringify(model.toJSON());
			if (DEBUG) {
				Ti.API.info("[SQL REST API - CREATE] options: ");
				Ti.API.info(params);
			}
			apiCall(params, function(_response) {
				if (_response.success) {
					var data = parseJSON(_response, parentNode);
					//Rest API should return a new model id.
					resp = saveData(data);
					_.isFunction(params.success) && params.success(resp);
				} else {
					//offline or error
					resp = saveData();
					if (_.isUndefined(_response.offline)) {
						// error
						_.isFunction(params.error) && params.error(returnErrorResponse ? _response : resp);
					} else {
						//offline - still a data success
						_.isFunction(params.success) && params.success(resp);
					}
				}
			});
			break;
		case 'read':
		
			if (!isCollection && model.id) {
				// find model by id
				params.url = params.url + '/' + model.id;
			}

			if (params.search) {
				// search mode
				params.returnExactServerResponse = true;
				params.url = params.url + "/search/" + Ti.Network.encodeURIComponent(params.search);
			}

			if (params.urlparams) {
				// build url with parameters
				params.url = encodeData(params.urlparams, params.url);
			}
			
			// check is all the necessary info is in place for last modified
			if (lastModifiedColumn && addModifedToUrl && lastModifiedValue) {
				// add last modified date to url
				var obj = {};
				obj[lastModifiedColumn] = lastModifiedValue;
				params.url = encodeData(obj, params.url);
			}

			if (DEBUG) {
				Ti.API.info("[SQL REST API - READ] options: ");
				Ti.API.info(params);
			}

			if (!params.localOnly && (params.initFetchWithLocalData || initFetchWithLocalData)) {
				// read local data before receiving server data
				resp = readSQL();
				_.isFunction(params.success) && params.success(resp);
				model.trigger("fetch", {
					serverData : false
				});
			}

			apiCall(params, function(_response) {
				if (_response.success) {
					if(deleteAllOnFetch){
						deleteAllSQL();
					}
					
					var data = parseJSON(_response, parentNode);
					if (_.isUndefined(params.localOnly)) {
						//we dont want to manipulate the data on localOnly requests
						saveData(data);
					}
					resp = readSQL(data);
					_.isFunction(params.success) && params.success(resp);
					model.trigger("fetch");
				} else {
					//error or offline - read local data
					resp = readSQL();
					if (_.isUndefined(_response.offline)) {
						//error
						_.isFunction(params.error) && params.error(returnErrorResponse ? _response : resp);
					} else {
						//offline - still a data success
						_.isFunction(params.success) && params.success(resp);
						model.trigger("fetch");
					}
				}
			});

			break;

		case 'update':
			if (!model.id) {
				params.error(null, "MISSING MODEL ID");
				Ti.API.error("[SQL REST API] ERROR: MISSING MODEL ID");
				return;
			}

			// setup the url & data
			if (_.indexOf(params.url, "?") == -1) {
				params.url = params.url + '/' + model.id;
			} else {
				var str = params.url.split("?");
				params.url = str[0] + '/' + model.id + "?" + str[1];
			}

			if (params.urlparams) {
				params.url = encodeData(params.urlparams, params.url);
			}

			params.data = JSON.stringify(model.toJSON());
			if (DEBUG) {
				Ti.API.info("[SQL REST API - UPDATE] options: ");
				Ti.API.info(params);
			}
			apiCall(params, function(_response) {
				if (_response.success) {
					var data = parseJSON(_response, parentNode);
					resp = saveData(data);
					_.isFunction(params.success) && params.success(resp);
				} else {
					//error or offline - save & use local data
					resp = saveData();
					if (_.isUndefined(_response.offline)) {
						//error
						_.isFunction(params.error) && params.error(returnErrorResponse ? _response : resp);
					} else {
						//offline - still a data success
						_.isFunction(params.success) && params.success(resp);
					}
				}
			});
			break;
		case 'delete':
			if (!model.id) {
				params.error(null, "MISSING MODEL ID");
				Ti.API.error("[SQL REST API] ERROR: MISSING MODEL ID");
				return;
			}
			params.url = params.url + '/' + model.id;

			if (DEBUG) {
				Ti.API.info("[SQL REST API - DELETE] options: ");
				Ti.API.info(params);
			}
			apiCall(params, function(_response) {
				if (_response.success) {
					var data = parseJSON(_response, parentNode);
					resp = deleteSQL();
					_.isFunction(params.success) && params.success(resp);
				} else {
					resp = deleteSQL();
					if (_.isUndefined(_response.offline)) {
						//error
						_.isFunction(params.error) && params.error(returnErrorResponse ? _response : resp);
					} else {
						//offline - still a data success
						_.isFunction(params.success) && params.success(resp);
					}
				}
			});
			break;
	}

	/////////////////////////////////////////////
	//SQL INTERFACE
	/////////////////////////////////////////////
	function saveData(data) {
		if (!data && !isCollection) {
			data = model.toJSON();
		}
		if (!data) {
			// its empty
			return;
		}
		if (!_.isArray(data)) {// its a model
			if (!_.isUndefined(data["is_deleted"])) {
				//delete item
				deleteSQL(data[model.idAttribute]);
			} else if (sqlFindItem(data[model.idAttribute]).length == 1) {
				//item exists - update it
				return updateSQL(data);
			} else {
				//write data to local sql
				return createSQL(data);
			}
		} else {//its an array of models
			var currentModels = sqlCurrentModels();
			for (var i in data) {
				if (!_.isUndefined(data[i]["is_deleted"])) {
					//delete item
					deleteSQL(data[i][model.idAttribute]);
				} else if (_.indexOf(currentModels, data[i][model.idAttribute]) != -1) {
					//item exists - update it
					updateSQL(data[i]);
				} else {
					//write data to local sql
					createSQL(data[i]);
				}
			}
		}
	}

	function createSQL(data) {
		var attrObj = {};

		if (DEBUG) {
			Ti.API.debug("[SQL REST API] createSQL data:");
			Ti.API.debug(data);
		}

		if (data) {
			attrObj = data;
		} else {
			if (!isCollection) {
				attrObj = model.toJSON();
			} else {
				Ti.API.error("[SQL REST API] Its a collection - error !");
			}
		}

		if (!attrObj[model.idAttribute]) {
			if (model.idAttribute === ALLOY_ID_DEFAULT) {
				// alloy-created GUID field
				attrObj.id = util.guid();
				attrObj[model.idAttribute] = attrObj.id;
			} else {
				// idAttribute not assigned by alloy. Leave it empty and
				// allow sqlite to process as null, which is the
				// expected value for an AUTOINCREMENT field.
				attrObj[model.idAttribute] = null;
			}
		}

		//validate the item
		if (useStrictValidation) {
			for (var c in columns) {
				if (c == model.idAttribute) {
					continue;
				}
				if (!_.contains(_.keys(attrObj), c)) {
					Ti.API.error("[SQL REST API] ITEM NOT VALID - REASON: " + c + " is not present");
					return;
				}
			}
		}

		// Create arrays for insert query
		var names = [], values = [], q = [];
		for (var k in columns) {
			names.push(k);
			if (_.isObject(attrObj[k])) {
				values.push(JSON.stringify(attrObj[k]));
			} else {
				values.push(attrObj[k]);
			}
			q.push('?');
		}
		// Last Modified logic
		// 
		if (lastModifiedColumn && _.isUndefined(params.disableLastModified)) {
			values[_.indexOf(names, lastModifiedColumn)] = lastModifiedDateFormat ? moment().format(lastModifiedDateFormat) : moment().format('YYYY-MM-DD HH:mm:ss');
		}

		// Assemble create query
		var sqlInsert = "INSERT INTO " + table + " (" + names.join(",") + ") VALUES (" + q.join(",") + ");";

		// execute the query and return the response
		db = Ti.Database.open(dbName);
		db.execute('BEGIN;');
		db.execute(sqlInsert, values);

		// get the last inserted id
		if (model.id === null) {
			var sqlId = "SELECT last_insert_rowid();";
			var rs = db.execute(sqlId);
			if (rs.isValidRow()) {
				model.id = rs.field(0);
				attrObj[model.idAttribute] = model.id;
			} else {
				Ti.API.warn('Unable to get ID from database for model: ' + model.toJSON());
			}
		}

		db.execute('COMMIT;');
		db.close();

		return attrObj;
	}

	function readSQL(data) {
		if (DEBUG) {
			Ti.API.debug("[SQL REST API] readSQL");
		}
		var sql = opts.query || 'SELECT * FROM ' + table;

		// we want the exact server response returned by the adapter
		if (params.returnExactServerResponse && data) {
			opts.sql = opts.sql || {};
			opts.sql.where = opts.sql.where || {};

			if (_.isEmpty(data)) {
				// No result
				opts.sql.where.id = "1=2";
			} else {
				// Find all idAttribute in the server response
				var ids = [];
				_.each(data, function(element) {
					ids.push(element[model.idAttribute]);
				});
				// this will select IDs in the sql query
				opts.sql.where.id = ids;
			}
		}

		// execute the select query
		db = Ti.Database.open(dbName);

		if (opts.query) {
			var rs = db.execute(opts.query.sql, opts.query.params);
		} else {
			if (opts.data) {//extend sql where with data
				opts.sql = opts.sql || {};
				opts.sql.where = opts.sql.where || {};
				_.extend(opts.sql.where, opts.data);
			}
			var sql = _buildQuery(table, opts.sql || opts);
			if (DEBUG) {
				Ti.API.debug("[SQL REST API] SQL QUERY: " + sql);
			}
			var rs = db.execute(sql);
		}
		var len = 0;
		var values = [];

		// iterate through all queried rows
		while (rs.isValidRow()) {
			var o = {};
			var fc = 0;

			fc = _.isFunction(rs.fieldCount) ? rs.fieldCount() : rs.fieldCount;

			// create list of rows returned from query
			_.times(fc, function(c) {
				var fn = rs.fieldName(c);
				o[fn] = rs.fieldByName(fn);
			});
			values.push(o);
			if (isCollection) {
				//push the models
				var m = new model.config.Model(o);
				model.models.push(m);
			}
			len++;
			rs.next();
		}

		// close off db after read query
		rs.close();
		db.close();

		// shape response based on whether it's a model or collection
		model.length = len;

		if (DEBUG) {
			Ti.API.debug("readSQL length: " + len);
		}
		return len === 1 ? resp = values[0] : resp = values;
	}

	function updateSQL(data) {
		var attrObj = {};
		if (DEBUG) {
			Ti.API.debug("updateSQL data:");
			Ti.API.debug(data);
		}
		if (data) {
			attrObj = data;
		} else {
			if (!isCollection) {
				attrObj = model.toJSON();
			} else {
				Ti.API.error("Its a collection - error!");
			}
		}

		// Create arrays for insert query
		var names = [], values = [], q = [];
		for (var k in columns) {
			if (!_.isUndefined(attrObj[k])) {//only update those who are in the data
				names.push(k + '=?');
				if (_.isObject(attrObj[k])) {
					values.push(JSON.stringify(attrObj[k]));
				} else {
					values.push(attrObj[k]);
				}
				q.push('?');
			}
		}

		// compose the update query
		var sql = 'UPDATE ' + table + ' SET ' + names.join(',') + ' WHERE ' + model.idAttribute + '=?';
		values.push(attrObj[model.idAttribute]);
		if (DEBUG) {
			Ti.API.debug("updateSQL sql: " + sql);
			Ti.API.debug(values);
		}
		// execute the update
		db = Ti.Database.open(dbName);
		db.execute(sql, values);
		
		if (lastModifiedColumn && _.isUndefined(params.disableLastModified)) {
			var updateSQL = "UPDATE " + table + " SET " + lastModifiedColumn + " = DATETIME('NOW') WHERE " + model.idAttribute + "=?";
			db.execute(updateSQL, attrObj[model.idAttribute]);
		}

		db.close();

		return attrObj;
	}

	function deleteSQL(id) {
		var sql = 'DELETE FROM ' + table + ' WHERE ' + model.idAttribute + '=?';
		// execute the delete
		db = Ti.Database.open(dbName);
		db.execute(sql, id || model.id);
		db.close();

		model.id = null;
		return model.toJSON();
	}
	
	function deleteAllSQL(){
		var sql = 'DELETE FROM ' + table;
		db = Ti.Database.open(dbName);
		db.execute(sql);
		db.close();
	}

	function sqlCurrentModels() {
		var sql = 'SELECT ' + model.idAttribute + ' FROM ' + table;
		db = Ti.Database.open(dbName);
		var rs = db.execute(sql);
		var output = [];
		while (rs.isValidRow()) {
			output.push(rs.fieldByName(model.idAttribute));
			rs.next();
		}
		rs.close();
		db.close();
		return output;
	}

	function sqlFindItem(_id) {
		var sql = 'SELECT ' + model.idAttribute + ' FROM ' + table + ' WHERE ' + model.idAttribute + '=?';
		db = Ti.Database.open(dbName);
		var rs = db.execute(sql, _id);
		var output = [];
		while (rs.isValidRow()) {
			output.push(rs.fieldByName(model.idAttribute));
			rs.next();
		}
		rs.close();
		db.close();
		return output;
	}

	function sqlLastModifiedItem() {
		if (singleModelRequest || !isCollection) {
			//model
			var sql = 'SELECT ' + lastModifiedColumn + ' FROM ' + table + ' WHERE ' + lastModifiedColumn + ' IS NOT NULL AND ' + model.idAttribute + '=' + singleModelRequest + ' ORDER BY ' + lastModifiedColumn + ' DESC LIMIT 0,1';
		} else {
			//collection
			var sql = 'SELECT ' + lastModifiedColumn + ' FROM ' + table + ' WHERE ' + lastModifiedColumn + ' IS NOT NULL ORDER BY ' + lastModifiedColumn + ' DESC LIMIT 0,1';
		}

		db = Ti.Database.open(dbName);
		rs = db.execute(sql);
		var output = null;
		if (rs.isValidRow()) {
			output = rs.field(0);
		}
		rs.close();
		db.close();
		return output;
	}

	function parseJSON(_response, parentNode) {
		var data = _response.responseJSON; //JSON.parse(_response.responseText);
		if (!_.isUndefined(parentNode)) {
			data = _.isFunction(parentNode) ? parentNode(data) : traverseProperties(data, parentNode);
		}
		if (DEBUG) {
			Ti.API.info("[SQL REST API] server response: ");
			Ti.API.debug(data);
		}
		return data;
	}

}
Example #12
0
function encodeData(obj, url) {
    var str = [];
    for (var p in obj) str.push(Ti.Network.encodeURIComponent(p) + "=" + Ti.Network.encodeURIComponent(obj[p]));
    return -1 == _.indexOf(url, "?") ? url + "?" + str.join("&") : url + "&" + str.join("&");
}
Example #13
0
function Sync(method, model, opts) {
    var DEBUG = model.config.debug;
    model.config.URL && model.config.URL.indexOf("{apiToken}") > 0 && (model.config.URL = model.config.URL.replace("{apiToken}", Alloy.Globals.core.apiToken()));
    model.idAttribute = model.config.adapter.idAttribute || "id";
    var parentNode = model.config.parentNode;
    var methodMap = {
        create: "POST",
        read: "GET",
        update: "PUT",
        "delete": "DELETE"
    };
    var type = methodMap[method];
    var params = _.extend({}, opts);
    params.type = type;
    model.config.type && (params.type = model.config.type);
    params.headers = params.headers || {};
    if (model.config.hasOwnProperty("headers")) for (var header in model.config.headers) params.headers[header] = model.config.headers[header];
    if (!params.url) {
        params.url = model.config.URL || model.url();
        if (!params.url) {
            Ti.API.error("[REST API] ERROR: NO BASE URL");
            model.config.URL = model.config.URL.replace(Alloy.Globals.core.apiToken(), "{apiToken}");
            return;
        }
    }
    if (Alloy.Backbone.emulateJSON) {
        params.contentType = "application/x-www-form-urlencoded";
        params.processData = true;
        params.data = params.data ? {
            model: params.data
        } : {};
    }
    if (Alloy.Backbone.emulateHTTP && ("PUT" === type || "DELETE" === type)) {
        Alloy.Backbone.emulateJSON && (params.data._method = type);
        params.type = "POST";
        params.beforeSend = function() {
            params.headers["X-HTTP-Method-Override"] = type;
        };
    }
    params.headers["Content-Type"] = "application/json";
    logger(DEBUG, "REST METHOD", method);
    switch (method) {
      case "create":
        params.data = JSON.stringify(model.toJSON());
        logger(DEBUG, "create options", params);
        apiCall(params, function(_response) {
            if (_response.success) {
                var data = parseJSON(DEBUG, _response, parentNode);
                data && void 0 === data[model.idAttribute] && (data[model.idAttribute] = guid());
                params.success(data, JSON.stringify(data));
                model.trigger("fetch");
            } else {
                params.error(_response.responseJSON, _response.responseText);
                Ti.API.error("[REST API] CREATE ERROR: ");
                Ti.API.error(_response);
            }
        });
        break;

      case "read":
        model[model.idAttribute] && (params.url = params.url + "/" + model[model.idAttribute]);
        params.urlparams && (params.url = encodeData(params.urlparams, params.url));
        logger(DEBUG, "read options", params);
        apiCall(params, function(_response) {
            if (_response.success) {
                var data = parseJSON(DEBUG, _response, parentNode);
                var values = [];
                model.length = 0;
                for (var i in data) {
                    var item = {};
                    item = data[i];
                    item && void 0 === item[model.idAttribute] && (item[model.idAttribute] = guid());
                    values.push(item);
                    model.length++;
                }
                params.success(1 === model.length ? values[0] : values, _response.responseText);
                model.trigger("fetch");
            } else {
                params.error(_response.responseJSON, _response.responseText);
                Ti.API.error("[REST API] READ ERROR: ");
                Ti.API.error(_response);
            }
        });
        break;

      case "update":
        if (!model.get(model.idAttribute)) {
            params.error(null, "MISSING MODEL ID");
            Ti.API.error("[REST API] ERROR: MISSING MODEL ID");
            model.config.URL = model.config.URL.replace(Alloy.Globals.core.apiToken(), "{apiToken}");
            return;
        }
        if (-1 == _.indexOf(params.url, "?")) params.url = params.url + "/" + model[model.idAttribute]; else {
            var str = params.url.split("?");
            params.url = str[0] + "/" + model[model.idAttribute] + "?" + str[1];
        }
        params.urlparams && (params.url = encodeData(params.urlparams, params.url));
        params.data = JSON.stringify(model.toJSON());
        logger(DEBUG, "update options", params);
        apiCall(params, function(_response) {
            if (_response.success) {
                var data = parseJSON(DEBUG, _response, parentNode);
                params.success(data, JSON.stringify(data));
                model.trigger("fetch");
            } else {
                params.error(_response.responseJSON, _response.responseText);
                Ti.API.error("[REST API] UPDATE ERROR: ");
                Ti.API.error(_response);
            }
        });
        break;

      case "delete":
        if (!model[model.idAttribute]) {
            params.error(null, "MISSING MODEL ID");
            Ti.API.error("[REST API] ERROR: MISSING MODEL ID");
            model.config.URL = model.config.URL.replace(Alloy.Globals.core.apiToken(), "{apiToken}");
            return;
        }
        params.url = params.url + "/" + model[model.idAttribute];
        logger(DEBUG, "delete options", params);
        apiCall(params, function(_response) {
            if (_response.success) {
                {
                    parseJSON(DEBUG, _response, parentNode);
                }
                params.success(null, _response.responseText);
                model.trigger("fetch");
            } else {
                params.error(_response.responseJSON, _response.responseText);
                Ti.API.error("[REST API] DELETE ERROR: ");
                Ti.API.error(_response);
            }
        });
    }
    Alloy.Globals.core.apiToken() && (model.config.URL = model.config.URL.replace(Alloy.Globals.core.apiToken(), "{apiToken}"));
}
function Sync(method, model, opts) {
	model.idAttribute = model.config.adapter.idAttribute || "id";

	// Debug mode
	var DEBUG = model.config.debug;

	// eTag enabled
	var eTagEnabled = model.config.eTagEnabled;

	// Used for custom parsing of the response data
	var parentNode = model.config.parentNode;

	// REST - CRUD
	var methodMap = {
		'create' : 'POST',
		'read' : 'GET',
		'update' : 'PUT',
		'delete' : 'DELETE'
	};

	var type = methodMap[method];
	var params = _.extend({}, opts);
	params.type = type;

	//set default headers
	params.headers = params.headers || {};

	// Send our own custom headers
	if (model.config.hasOwnProperty("headers")) {
		for (var header in model.config.headers) {
			params.headers[header] = model.config.headers[header];
		}
	}

	// We need to ensure that we have a base url.
	if (!params.url) {
		params.url = (model.config.URL || model.url());
		if (!params.url) {
			Ti.API.error("[REST API] ERROR: NO BASE URL");
			return;
		}
	}

	// Extend the provided url params with those from the model config
	if (_.isObject(params.urlparams) || model.config.URLPARAMS) {
		_.extend(params.urlparams, _.isFunction(model.config.URLPARAMS) ? model.config.URLPARAMS() : model.config.URLPARAMS);
	}

	// For older servers, emulate JSON by encoding the request into an HTML-form.
	if (Alloy.Backbone.emulateJSON) {
		params.contentType = 'application/x-www-form-urlencoded';
		params.processData = true;
		params.data = params.data ? {
			model : params.data
		} : {};
	}

	// For older servers, emulate HTTP by mimicking the HTTP method with `_method`
	// And an `X-HTTP-Method-Override` header.
	if (Alloy.Backbone.emulateHTTP) {
		if (type === 'PUT' || type === 'DELETE') {
			if (Alloy.Backbone.emulateJSON)
				params.data._method = type;
			params.type = 'POST';
			params.beforeSend = function(xhr) {
				params.headers['X-HTTP-Method-Override'] = type;
			};
		}
	}

	//json data transfers
	params.headers['Content-Type'] = 'application/json';

	logger(DEBUG, "REST METHOD", method);

	switch(method) {
		case 'create' :
			// convert to string for API call
			params.data = JSON.stringify(model.toJSON());
			logger(DEBUG, "create options", params);

			apiCall(params, function(_response) {
				if (_response.success) {
					var data = parseJSON(DEBUG, _response, parentNode);

					//Rest API should return a new model id.
					if (data[model.idAttribute] === undefined) {
						//if not - create one
						data[model.idAttribute] = guid();
					}
					params.success(data, JSON.stringify(data));
					model.trigger("fetch");
					// fire event
				} else {
					params.error(_response.responseJSON, _response.responseText);
					Ti.API.error('[REST API] CREATE ERROR: ');
					Ti.API.error(_response);
				}
			});
			break;

		case 'read':
			if (model[model.idAttribute]) {
				params.url = params.url + '/' + model[model.idAttribute];
			}

			if (params.search) {
				// search mode
				params.url = params.url + "/search/" + Ti.Network.encodeURIComponent(params.search);
			}

			if (params.urlparams) {
				// build url with parameters
				params.url = encodeData(params.urlparams, params.url);
			}

			if (eTagEnabled) {
				params.eTagEnabled = true;
			}

			logger(DEBUG, "read options", params);

			apiCall(params, function(_response) {
				if (_response.success) {
					var data = parseJSON(DEBUG, _response, parentNode);
					var values = [];
					model.length = 0;
					for (var i in data) {
						var item = {};
						item = data[i];
						if (item[model.idAttribute] === undefined) {
							item[model.idAttribute] = guid();
						}
						values.push(item);
						model.length++;
					}

					params.success((model.length === 1) ? values[0] : values, _response.responseText);
					model.trigger("fetch");
				} else {
					params.error(model, _response.responseText);
					Ti.API.error('[REST API] READ ERROR: ');
					Ti.API.error(_response);
				}
			});
			break;

		case 'update' :
			if (!model[model.idAttribute]) {
				params.error(null, "MISSING MODEL ID");
				Ti.API.error("[REST API] ERROR: MISSING MODEL ID");
				return;
			}

			// setup the url & data
			if (_.indexOf(params.url, "?") == -1) {
				params.url = params.url + '/' + model[model.idAttribute];
			} else {
				var str = params.url.split("?");
				params.url = str[0] + '/' + model[model.idAttribute] + "?" + str[1];
			}

			if (params.urlparams) {
				params.url = encodeData(params.urlparams, params.url);
			}

			params.data = JSON.stringify(model.toJSON());

			logger(DEBUG, "update options", params);

			apiCall(params, function(_response) {
				if (_response.success) {
					var data = parseJSON(DEBUG, _response, parentNode);
					params.success(data, JSON.stringify(data));
					model.trigger("fetch");
				} else {
					params.error(model, _response.responseText);
					Ti.API.error('[REST API] UPDATE ERROR: ');
					Ti.API.error(_response);
				}
			});
			break;

		case 'delete' :
			if (!model[model.idAttribute]) {
				params.error(null, "MISSING MODEL ID");
				Ti.API.error("[REST API] ERROR: MISSING MODEL ID");
				return;
			}
			params.url = params.url + '/' + model[model.idAttribute];

			logger(DEBUG, "delete options", params);

			apiCall(params, function(_response) {
				if (_response.success) {
					var data = parseJSON(DEBUG, _response, parentNode);
					params.success(null, _response.responseText);
					model.trigger("fetch");
				} else {
					params.error(model, _response.responseText);
					Ti.API.error('[REST API] DELETE ERROR: ');
					Ti.API.error(_response);
				}
			});
			break;
	}

}
function Sync(method, model, opts) {
    model.idAttribute = model.config.adapter.idAttribute || "id";
    var DEBUG = model.config.debug;
    var eTagEnabled = model.config.eTagEnabled;
    var parentNode = model.config.parentNode;
    var methodMap = {
        create: "POST",
        read: "GET",
        update: "PUT",
        "delete": "DELETE"
    };
    var type = methodMap[method];
    var params = _.extend({}, opts);
    params.type = type;
    params.headers = params.headers || {};
    if (model.config.hasOwnProperty("headers")) for (var header in model.config.headers) params.headers[header] = model.config.headers[header];
    if (!params.url) {
        params.url = model.config.URL || model.url();
        if (!params.url) {
            Ti.API.error("[REST API] ERROR: NO BASE URL");
            return;
        }
    }
    if (_.isObject(params.urlparams) || model.config.URLPARAMS) {
        params.urlparams = params.urlparams || {};
        _.extend(params.urlparams, _.isFunction(model.config.URLPARAMS) ? model.config.URLPARAMS() : model.config.URLPARAMS);
    }
    if (Alloy.Backbone.emulateJSON) {
        params.contentType = "application/x-www-form-urlencoded";
        params.processData = true;
        params.data = params.data ? {
            model: params.data
        } : {};
    }
    if (Alloy.Backbone.emulateHTTP && ("PUT" === type || "DELETE" === type)) {
        Alloy.Backbone.emulateJSON && (params.data._method = type);
        params.type = "POST";
        params.beforeSend = function() {
            params.headers["X-HTTP-Method-Override"] = type;
        };
    }
    params.data || !model || "create" != method && "update" != method || (params.headers["Content-Type"] = "application/json");
    logger(DEBUG, "REST METHOD", method);
    switch (method) {
      case "create":
        params.data = JSON.stringify(model.toJSON());
        logger(DEBUG, "create options", params);
        apiCall(params, function(_response) {
            _response.success || (_response = fixResponseJSON(_response));
            if (_response.success) {
                var data = parseJSON(DEBUG, _response, parentNode, model);
                void 0 === data[model.idAttribute] && (data[model.idAttribute] = guid());
                params.success(data, JSON.stringify(data));
                model.trigger("fetch");
            } else {
                params.error(_response.responseJSON, _response.responseText);
                Ti.API.error("[REST API] CREATE ERROR: ");
                Ti.API.error(_response);
            }
        });
        break;

      case "read":
        model.id && (params.url = params.url + "/" + model.id);
        params.search && (params.url = params.url + "/search/" + Ti.Network.encodeURIComponent(params.search));
        params.urlparams && (params.url = encodeData(params.urlparams, params.url));
        !params.urlparams && params.data && (params.url = encodeData(params.data, params.url));
        eTagEnabled && (params.eTagEnabled = true);
        logger(DEBUG, "read options", params);
        apiCall(params, function(_response) {
            if (_response.success) {
                var data = parseJSON(DEBUG, _response, parentNode, model);
                var values = [];
                _.isArray(data) || (data = [ data ]);
                var length = 0;
                for (var i in data) {
                    var item = {};
                    item = data[i];
                    void 0 === item[model.idAttribute] && (item[model.idAttribute] = guid());
                    values.push(item);
                    length++;
                }
                params.success(1 === length ? values[0] : values, _response.responseText);
                model.trigger("fetch");
            } else {
                params.error(model, _response.responseText);
                Ti.API.error("[REST API] READ ERROR: ");
                Ti.API.error(_response);
            }
        });
        break;

      case "update":
        if (!model.id) {
            params.error(null, "MISSING MODEL ID");
            Ti.API.error("[REST API] ERROR: MISSING MODEL ID");
            return;
        }
        if (-1 == _.indexOf(params.url, "?")) params.url = params.url + "/" + model.id; else {
            var str = params.url.split("?");
            params.url = str[0] + "/" + model.id + "?" + str[1];
        }
        params.urlparams && (params.url = encodeData(params.urlparams, params.url));
        var nodeType = model.get("type") ? model.get("type") : "";
        if (nodeType.length > 0) var data = {
            type: nodeType
        }; else var data = {};
        for (changeAtt in params.changes) {
            Ti.API.info("changeAtt: " + changeAtt);
            data[changeAtt] = model.get(changeAtt);
        }
        params.data = JSON.stringify(data);
        logger(DEBUG, "update options", params);
        apiCall(params, function(_response) {
            if (_response.success) {
                var data = parseJSON(DEBUG, _response, parentNode, model);
                params.success(data, JSON.stringify(data));
                model.trigger("fetch");
            } else {
                params.error(model, _response.responseText);
                Ti.API.error("[REST API] UPDATE ERROR: ");
                Ti.API.error(_response);
            }
        });
        break;

      case "delete":
        if (!model.id) {
            params.error(null, "MISSING MODEL ID");
            Ti.API.error("[REST API] ERROR: MISSING MODEL ID");
            return;
        }
        if (-1 == _.indexOf(params.url, "?")) params.url = params.url + "/" + model.id; else {
            var str = params.url.split("?");
            params.url = str[0] + "/" + model.id + "?" + str[1];
        }
        logger(DEBUG, "delete options", params);
        apiCall(params, function(_response) {
            if (_response.success) {
                parseJSON(DEBUG, _response, parentNode, model);
                params.success(null, _response.responseText);
                model.trigger("fetch");
            } else {
                params.error(model, _response.responseText);
                Ti.API.error("[REST API] DELETE ERROR: ");
                Ti.API.error(_response);
            }
        });
    }
}
Example #16
0
function Sync(method, model, opts) {
    function saveData(data) {
        data || isCollection || (data = model.toJSON());
        if (!data) return;
        if (_.isArray(data)) {
            var currentModels = sqlCurrentModels();
            for (var i in data) _.isUndefined(data[i]["is_deleted"]) ? -1 != _.indexOf(currentModels, data[i][model.idAttribute]) ? updateSQL(data[i]) : createSQL(data[i]) : deleteSQL(data[i][model.idAttribute]);
        } else {
            if (_.isUndefined(data["is_deleted"])) return 1 == sqlFindItem(data[model.idAttribute]).length ? updateSQL(data) : createSQL(data);
            deleteSQL(data[model.idAttribute]);
        }
    }
    function createSQL(data) {
        var attrObj = {};
        if (DEBUG) {
            Ti.API.debug("[SQL REST API] createSQL data:");
            Ti.API.debug(data);
        }
        data ? attrObj = data : isCollection ? Ti.API.error("[SQL REST API] Its a collection - error !") : attrObj = model.toJSON();
        if (!attrObj[model.idAttribute]) if (model.idAttribute === ALLOY_ID_DEFAULT) {
            attrObj.id = guid();
            attrObj[model.idAttribute] = attrObj.id;
        } else attrObj[model.idAttribute] = null;
        if (useStrictValidation) for (var c in columns) {
            if (c == model.idAttribute) continue;
            if (!_.contains(_.keys(attrObj), c)) {
                Ti.API.error("[SQL REST API] ITEM NOT VALID - REASON: " + c + " is not present");
                return;
            }
        }
        var names = [], values = [], q = [];
        for (var k in columns) {
            names.push(k);
            _.isObject(attrObj[k]) ? values.push(JSON.stringify(attrObj[k])) : values.push(attrObj[k]);
            q.push("?");
        }
        lastModifiedColumn && _.isUndefined(params.disableLastModified) && (values[_.indexOf(names, lastModifiedColumn)] = lastModifiedDateFormat ? moment().format(lastModifiedDateFormat) : moment().format("YYYY-MM-DD HH:mm:ss"));
        var sqlInsert = "INSERT INTO " + table + " (" + names.join(",") + ") VALUES (" + q.join(",") + ");";
        db = Ti.Database.open(dbName);
        db.execute("BEGIN;");
        db.execute(sqlInsert, values);
        if (null === model.id) {
            var sqlId = "SELECT last_insert_rowid();";
            var rs = db.execute(sqlId);
            if (rs.isValidRow()) {
                model.id = rs.field(0);
                attrObj[model.idAttribute] = model.id;
            } else Ti.API.warn("Unable to get ID from database for model: " + model.toJSON());
        }
        db.execute("COMMIT;");
        db.close();
        return attrObj;
    }
    function readSQL(data) {
        DEBUG && Ti.API.debug("[SQL REST API] readSQL");
        var sql = opts.query || "SELECT * FROM " + table;
        if (params.returnExactServerResponse && data) {
            opts.sql = opts.sql || {};
            opts.sql.where = opts.sql.where || {};
            if (_.isEmpty(data)) opts.sql.where.id = "1=2"; else {
                var ids = [];
                _.each(data, function(element) {
                    ids.push(element[model.idAttribute]);
                });
                opts.sql.where.id = ids;
            }
        }
        db = Ti.Database.open(dbName);
        if (opts.query) var rs = db.execute(opts.query.sql, opts.query.params); else {
            if (opts.data) {
                opts.sql = opts.sql || {};
                opts.sql.where = opts.sql.where || {};
                _.extend(opts.sql.where, opts.data);
            }
            var sql = _buildQuery(table, opts.sql || opts);
            DEBUG && Ti.API.debug("[SQL REST API] SQL QUERY: " + sql);
            var rs = db.execute(sql);
        }
        var len = 0;
        var values = [];
        while (rs.isValidRow()) {
            var o = {};
            var fc = 0;
            fc = _.isFunction(rs.fieldCount) ? rs.fieldCount() : rs.fieldCount;
            _.times(fc, function(c) {
                var fn = rs.fieldName(c);
                o[fn] = rs.fieldByName(fn);
            });
            values.push(o);
            if (isCollection) {
                var m = new model.config.Model(o);
                model.models.push(m);
            }
            len++;
            rs.next();
        }
        rs.close();
        db.close();
        model.length = len;
        DEBUG && Ti.API.debug("readSQL length: " + len);
        return resp = 1 === len ? values[0] : values;
    }
    function updateSQL(data) {
        var attrObj = {};
        if (DEBUG) {
            Ti.API.debug("updateSQL data:");
            Ti.API.debug(data);
        }
        data ? attrObj = data : isCollection ? Ti.API.error("Its a collection - error!") : attrObj = model.toJSON();
        var names = [], values = [], q = [];
        for (var k in columns) if (!_.isUndefined(attrObj[k])) {
            names.push(k + "=?");
            _.isObject(attrObj[k]) ? values.push(JSON.stringify(attrObj[k])) : values.push(attrObj[k]);
            q.push("?");
        }
        var sql = "UPDATE " + table + " SET " + names.join(",") + " WHERE " + model.idAttribute + "=?";
        values.push(attrObj[model.idAttribute]);
        if (DEBUG) {
            Ti.API.debug("updateSQL sql: " + sql);
            Ti.API.debug(values);
        }
        db = Ti.Database.open(dbName);
        db.execute(sql, values);
        if (lastModifiedColumn && _.isUndefined(params.disableLastModified)) {
            var updateSQL = "UPDATE " + table + " SET " + lastModifiedColumn + " = DATETIME('NOW') WHERE " + model.idAttribute + "=?";
            db.execute(updateSQL, attrObj[model.idAttribute]);
        }
        db.close();
        return attrObj;
    }
    function deleteSQL(id) {
        var sql = "DELETE FROM " + table + " WHERE " + model.idAttribute + "=?";
        db = Ti.Database.open(dbName);
        db.execute(sql, id || model.id);
        db.close();
        model.id = null;
        return model.toJSON();
    }
    function deleteAllSQL() {
        var sql = "DELETE FROM " + table;
        db = Ti.Database.open(dbName);
        db.execute(sql);
        db.close();
    }
    function sqlCurrentModels() {
        var sql = "SELECT " + model.idAttribute + " FROM " + table;
        db = Ti.Database.open(dbName);
        var rs = db.execute(sql);
        var output = [];
        while (rs.isValidRow()) {
            output.push(rs.fieldByName(model.idAttribute));
            rs.next();
        }
        rs.close();
        db.close();
        return output;
    }
    function sqlFindItem(_id) {
        var sql = "SELECT " + model.idAttribute + " FROM " + table + " WHERE " + model.idAttribute + "=?";
        db = Ti.Database.open(dbName);
        var rs = db.execute(sql, _id);
        var output = [];
        while (rs.isValidRow()) {
            output.push(rs.fieldByName(model.idAttribute));
            rs.next();
        }
        rs.close();
        db.close();
        return output;
    }
    function sqlLastModifiedItem() {
        if (singleModelRequest || !isCollection) var sql = "SELECT " + lastModifiedColumn + " FROM " + table + " WHERE " + lastModifiedColumn + " IS NOT NULL AND " + model.idAttribute + "=" + singleModelRequest + " ORDER BY " + lastModifiedColumn + " DESC LIMIT 0,1"; else var sql = "SELECT " + lastModifiedColumn + " FROM " + table + " WHERE " + lastModifiedColumn + " IS NOT NULL ORDER BY " + lastModifiedColumn + " DESC LIMIT 0,1";
        db = Ti.Database.open(dbName);
        rs = db.execute(sql);
        var output = null;
        rs.isValidRow() && (output = rs.field(0));
        rs.close();
        db.close();
        return output;
    }
    function parseJSON(_response, parentNode) {
        var data = _response.responseJSON;
        _.isUndefined(parentNode) || (data = _.isFunction(parentNode) ? parentNode(data) : traverseProperties(data, parentNode));
        if (DEBUG) {
            Ti.API.info("[SQL REST API] server response: ");
            Ti.API.debug(data);
        }
        return data;
    }
    var db, table = model.config.adapter.collection_name, columns = model.config.columns, dbName = model.config.adapter.db_name || ALLOY_DB_DEFAULT, resp = null;
    model.idAttribute = model.config.adapter.idAttribute;
    var DEBUG = model.config.debug;
    var lastModifiedColumn = model.config.adapter.lastModifiedColumn;
    var addModifedToUrl = model.config.adapter.addModifedToUrl;
    var lastModifiedDateFormat = model.config.adapter.lastModifiedDateFormat;
    var parentNode = model.config.parentNode;
    var useStrictValidation = model.config.useStrictValidation;
    var initFetchWithLocalData = model.config.initFetchWithLocalData;
    var deleteAllOnFetch = model.config.deleteAllOnFetch;
    var isCollection = model instanceof Backbone.Collection ? true : false;
    var returnErrorResponse = model.config.returnErrorResponse;
    var singleModelRequest = null;
    if (lastModifiedColumn) {
        opts.sql && opts.sql.where && (singleModelRequest = opts.sql.where[model.idAttribute]);
        !singleModelRequest && opts.data && opts.data[model.idAttribute] && (singleModelRequest = opts.data[model.idAttribute]);
    }
    var methodMap = {
        create: "POST",
        read: "GET",
        update: "PUT",
        "delete": "DELETE"
    };
    var type = methodMap[method];
    var params = _.extend({}, opts);
    params.type = type;
    params.headers = params.headers || {};
    if (model.config.hasOwnProperty("headers")) for (header in model.config.headers) params.headers[header] = model.config.headers[header];
    if (!params.url) {
        params.url = model.config.URL || model.url();
        if (!params.url) {
            Ti.API.error("[SQL REST API] ERROR: NO BASE URL");
            return;
        }
    }
    if (lastModifiedColumn && _.isUndefined(params.disableLastModified)) {
        var lastModifiedValue = "";
        try {
            lastModifiedValue = sqlLastModifiedItem();
        } catch (e) {
            DEBUG && Ti.API.debug("[SQL REST API] LASTMOD SQL FAILED: ");
        }
        params.headers["Last-Modified"] = lastModifiedValue;
    }
    if (Alloy.Backbone.emulateJSON) {
        params.contentType = "application/x-www-form-urlencoded";
        params.processData = true;
        params.data = params.data ? {
            model: params.data
        } : {};
    }
    if (Alloy.Backbone.emulateHTTP && ("PUT" === type || "DELETE" === type)) {
        Alloy.Backbone.emulateJSON && (params.data._method = type);
        params.type = "POST";
        params.beforeSend = function() {
            params.headers["X-HTTP-Method-Override"] = type;
        };
    }
    params.headers["Content-Type"] = "application/json";
    DEBUG && Ti.API.debug("[SQL REST API] REST METHOD: " + method);
    switch (method) {
      case "create":
        params.data = JSON.stringify(model.toJSON());
        if (DEBUG) {
            Ti.API.info("[SQL REST API] options: ");
            Ti.API.info(params);
        }
        apiCall(params, function(_response) {
            if (_response.success) {
                var data = parseJSON(_response, parentNode);
                resp = saveData(data);
                _.isFunction(params.success) && params.success(resp);
            } else {
                resp = saveData();
                _.isUndefined(_response.offline) ? _.isFunction(params.error) && params.error(returnErrorResponse ? _response : resp) : _.isFunction(params.success) && params.success(resp);
            }
        });
        break;

      case "read":
        !isCollection && model.id && (params.url = params.url + "/" + model.id);
        if (params.search) {
            params.returnExactServerResponse = true;
            params.url = params.url + "/search/" + Ti.Network.encodeURIComponent(params.search);
        }
        params.urlparams && (params.url = encodeData(params.urlparams, params.url));
        if (lastModifiedColumn && addModifedToUrl && lastModifiedValue) {
            var obj = {};
            obj[lastModifiedColumn] = lastModifiedValue;
            params.url = encodeData(obj, params.url);
        }
        if (DEBUG) {
            Ti.API.info("[SQL REST API] options: ");
            Ti.API.info(params);
        }
        if (!params.localOnly && (params.initFetchWithLocalData || initFetchWithLocalData)) {
            resp = readSQL();
            _.isFunction(params.success) && params.success(resp);
            model.trigger("fetch", {
                serverData: false
            });
        }
        apiCall(params, function(_response) {
            if (_response.success) {
                deleteAllOnFetch && deleteAllSQL();
                var data = parseJSON(_response, parentNode);
                _.isUndefined(params.localOnly) && saveData(data);
                resp = readSQL(data);
                _.isFunction(params.success) && params.success(resp);
                model.trigger("fetch");
            } else {
                resp = readSQL();
                if (_.isUndefined(_response.offline)) _.isFunction(params.error) && params.error(returnErrorResponse ? _response : resp); else {
                    _.isFunction(params.success) && params.success(resp);
                    model.trigger("fetch");
                }
            }
        });
        break;

      case "update":
        if (!model.id) {
            params.error(null, "MISSING MODEL ID");
            Ti.API.error("[SQL REST API] ERROR: MISSING MODEL ID");
            return;
        }
        if (-1 == _.indexOf(params.url, "?")) params.url = params.url + "/" + model.id; else {
            var str = params.url.split("?");
            params.url = str[0] + "/" + model.id + "?" + str[1];
        }
        params.urlparams && (params.url = encodeData(params.urlparams, params.url));
        params.data = JSON.stringify(model.toJSON());
        if (DEBUG) {
            Ti.API.info("[SQL REST API] options: ");
            Ti.API.info(params);
        }
        apiCall(params, function(_response) {
            if (_response.success) {
                var data = parseJSON(_response, parentNode);
                resp = saveData(data);
                _.isFunction(params.success) && params.success(resp);
            } else {
                resp = saveData();
                _.isUndefined(_response.offline) ? _.isFunction(params.error) && params.error(returnErrorResponse ? _response : resp) : _.isFunction(params.success) && params.success(resp);
            }
        });
        break;

      case "delete":
        if (!model.id) {
            params.error(null, "MISSING MODEL ID");
            Ti.API.error("[SQL REST API] ERROR: MISSING MODEL ID");
            return;
        }
        params.url = params.url + "/" + model.id;
        if (DEBUG) {
            Ti.API.info("[SQL REST API] options: ");
            Ti.API.info(params);
        }
        apiCall(params, function(_response) {
            if (_response.success) {
                parseJSON(_response, parentNode);
                resp = deleteSQL();
                _.isFunction(params.success) && params.success(resp);
            } else {
                resp = deleteSQL();
                _.isUndefined(_response.offline) ? _.isFunction(params.error) && params.error(returnErrorResponse ? _response : resp) : _.isFunction(params.success) && params.success(resp);
            }
        });
    }
}
Example #17
0
function Sync(method, model, opts) {
	var table = model.config.adapter.collection_name,
	    columns = model.config.columns,
	    dbName = model.config.adapter.db_name || ALLOY_DB_DEFAULT,
	    resp = null,
	    db;

	model.idAttribute = model.config.adapter.idAttribute || "id";
	model.deletedAttribute = model.config.adapter.deletedAttribute || 'is_deleted';

	// Debug mode
	var DEBUG = opts.debug || model.config.debug;

	// Are we dealing with a colleciton or a model?
	var isCollection = ( model instanceof Backbone.Collection) ? true : false;

	var singleModelRequest = null;
	if (model.config.adapter.lastModifiedColumn) {
		if (opts.sql && opts.sql.where) {
			singleModelRequest = opts.sql.where[model.idAttribute];
		}
		if (!singleModelRequest && opts.data && opts.data[model.idAttribute]) {
			singleModelRequest = opts.data[model.idAttribute];
		}
	}

	var params = _.extend({}, opts);

	// fill params with default values
	_.defaults(params, {

		// Last modified logic
		lastModifiedColumn : model.config.adapter.lastModifiedColumn,
		addModifedToUrl : model.config.adapter.addModifedToUrl,
		lastModifiedDateFormat : model.config.adapter.lastModifiedDateFormat,
		singleModelRequest : singleModelRequest,

		// eTag
		eTagEnabled : model.config.eTagEnabled,

		// Used for custom parsing of the response data
		parentNode : model.config.parentNode,

		// Validate the response data and only allow those items with all columns defined in the object to be saved to the database.
		useStrictValidation : model.config.useStrictValidation,

		// before fethcing data from remote server - the adapter will return the stored data if enabled
		initFetchWithLocalData : model.config.initFetchWithLocalData,

		// If enabled - it will delete all the rows in the table on a successful fetch
		deleteAllOnFetch : model.config.deleteAllOnFetch,

		// If enabled - it will delete rows based a sql query on a successful fetch
		deleteSQLOnFetch : model.config.deleteSQLOnFetch,

		// Save data locally on server error?
		disableSaveDataLocallyOnServerError : model.config.disableSaveDataLocallyOnServerError,

		// Return the exact error reponse
		returnErrorResponse : model.config.returnErrorResponse,

		// Request params
		requestparams : model.config.requestparams,

		// xhr settings
		timeout: 7000,
		cache: false,
		validatesSecureCertificate: ENV_PROD ? true : false
	});

	// REST API - set the type
	var methodMap = {
		'create' : 'POST',
		'read' : 'GET',
		'update' : 'PUT',
		'delete' : 'DELETE'
	};
	var type = methodMap[method];
	params.type = type;

	// set default headers
	params.headers = params.headers || {};

	// process the runtime params
	for (var header in params.headers) {
		params.headers[header] = _.isFunction(params.headers[header]) ? params.headers[header]() : params.headers[header];
	}
	// Send our own custom headers
	if (model.config.hasOwnProperty("headers")) {
		for (var header in model.config.headers) {
			// only process headers from model config if not provided through runtime params
			if (!params.headers[header]) {
				params.headers[header] = _.isFunction(model.config.headers[header]) ? model.config.headers[header]() : model.config.headers[header];
			}
		}
	}

	// We need to ensure that we have a base url.
	if (!params.url) {
		model.config.URL = _.isFunction(model.config.URL) ? model.config.URL() : model.config.URL;
		params.url = (model.config.URL || model.url());
		if (!params.url) {
			Ti.API.error("[SQL REST API] ERROR: NO BASE URL");
			return;
		}
	}

	// Check if Last Modified is active
	if (params.lastModifiedColumn && _.isUndefined(params.disableLastModified)) {
		//send last modified model datestamp to the remote server
		params.lastModifiedValue = null;
		try {
			params.lastModifiedValue = sqlLastModifiedItem();
		} catch (e) {
			logger(DEBUG, "LASTMOD SQL FAILED: ");

		}
		if (params.lastModifiedValue) {
			params.headers['If-Modified-Since'] = params.lastModifiedValue;
		}
	}

	// Extend the provided url params with those from the model config
	if (_.isObject(params.urlparams) || model.config.URLPARAMS) {
		if(_.isUndefined(params.urlparams)) {
			params.urlparams = {};
		}
		_.extend(params.urlparams, _.isFunction(model.config.URLPARAMS) ? model.config.URLPARAMS() : model.config.URLPARAMS);
	}

	// parse url {requestparams}
	_.each(params.requestparams, function(value,key) {
		params.url = params.url.replace('{' + key + '}', value ? escape(value) : '', "gi");
	});

	// For older servers, emulate JSON by encoding the request into an HTML-form.
	if (Alloy.Backbone.emulateJSON) {
		params.contentType = 'application/x-www-form-urlencoded';
		params.processData = true;
		params.data = params.data ? {
			model : params.data
		} : {};
	}

	// For older servers, emulate HTTP by mimicking the HTTP method with `_method`
	// And an `X-HTTP-Method-Override` header.
	if (Alloy.Backbone.emulateHTTP) {
		if (type === 'PUT' || type === 'DELETE') {
			if (Alloy.Backbone.emulateJSON)
				params.data._method = type;
			params.type = 'POST';
			params.beforeSend = function(xhr) {
				params.headers['X-HTTP-Method-Override'] = type;
			};
		}
	}

	// json data transfers
	params.headers['Content-Type'] = 'application/json';

	logger(DEBUG, "REST METHOD: " + method);

	switch (method) {
	case 'create':
		// convert to string for API call
		params.data = JSON.stringify(model.toJSON());
		logger(DEBUG, "create options", params);

		apiCall(params, function(_response) {
			if (_response.success) {
				var data = parseJSON(_response, params.parentNode);
				// Rest API should return a new model id.
				resp = saveData(data);
				_.isFunction(params.success) && params.success(resp);
			} else {
				// offline or error

				// save data locally when server returned an error
				if (!_response.localOnly && params.disableSaveDataLocallyOnServerError) {
					logger(DEBUG, "NOTICE: The data is not being saved locally");
				} else {
					resp = saveData();
				}

				if (_.isUndefined(_response.offline)) {
					// error
					_.isFunction(params.error) && params.error( params.returnErrorResponse ? _response : resp);
				} else {
					//offline - still a data success
					_.isFunction(params.success) && params.success(resp);
				}
			}
		});
		break;
	case 'read':

		if (!isCollection && model.id) {
			// find model by id
			params.url = params.url + '/' + model.id;
		}

		if (params.search) {
			// search mode
			params.returnExactServerResponse = true;
			params.url = params.url + "/search/" + Ti.Network.encodeURIComponent(params.search);
		}

		if (params.urlparams) {
			// build url with parameters
			params.url = encodeData(params.urlparams, params.url);
		}

		// check is all the necessary info is in place for last modified
		if (params.lastModifiedColumn && params.addModifedToUrl && params.lastModifiedValue) {
			// add last modified date to url
			var obj = {};
			obj[params.lastModifiedColumn] = params.lastModifiedValue;
			params.url = encodeData(obj, params.url);
		}

		logger(DEBUG, "read options", params);

		if (!params.localOnly && params.initFetchWithLocalData) {
			// read local data before receiving server data
			resp = readSQL();
			_.isFunction(params.success) && params.success(resp);
			model.trigger("fetch", {
				serverData : false
			});
		}

		apiCall(params, function(_response) {
			if (_response.success) {
				if (_response.code != 304) {

					// delete all rows
					if (params.deleteAllOnFetch) {
						deleteAllSQL();
					}

					// delete on sql query
					if (params.deleteSQLOnFetch) {
						deleteBasedOnSQL(params.deleteSQLOnFetch);
					}

					// parse data
					var data = parseJSON(_response, params.parentNode);
					if (!params.localOnly) {
						//we dont want to manipulate the data on localOnly requests
						saveData(data);
					}
				}
				resp = readSQL(data);
				_.isFunction(params.success) && params.success(resp);
				model.trigger("fetch");
			} else {
				//error or offline - read local data
				if (!params.localOnly && params.initFetchWithLocalData) {
				} else {
					resp = readSQL();
				}
				if (_.isUndefined(_response.offline)) {
					//error
					_.isFunction(params.error) && params.error( params.returnErrorResponse ? _response : resp);
				} else {
					//offline - still a data success
					_.isFunction(params.success) && params.success(resp);
					model.trigger("fetch");
				}
			}
		});

		break;

	case 'update':
		if (!model.id) {
			params.error(null, "MISSING MODEL ID");
			Ti.API.error("[SQL REST API] ERROR: MISSING MODEL ID");
			return;
		}

		// setup the url & data
		if (_.indexOf(params.url, "?") == -1) {
			params.url = params.url + '/' + model.id;
		} else {
			var str = params.url.split("?");
			params.url = str[0] + '/' + model.id + "?" + str[1];
		}

		if (params.urlparams) {
			params.url = encodeData(params.urlparams, params.url);
		}

		params.data = JSON.stringify(model.toJSON());
		logger(DEBUG, "update options", params);

		apiCall(params, function(_response) {
			if (_response.success) {
				var data = parseJSON(_response, params.parentNode);
				resp = saveData(data);
				_.isFunction(params.success) && params.success(resp);
			} else {
				// error or offline - save & use local data

				// save data locally when server returned an error
				if (!_response.localOnly && params.disableSaveDataLocallyOnServerError) {
					logger(DEBUG, "NOTICE: The data is not being saved locally");
				} else {
					resp = saveData();
				}

				if (_.isUndefined(_response.offline)) {
					//error
					_.isFunction(params.error) && params.error( params.returnErrorResponse ? _response : resp);
				} else {
					//offline - still a data success
					_.isFunction(params.success) && params.success(resp);
				}
			}
		});
		break;
	case 'delete':
		if (!model.id) {
			params.error(null, "MISSING MODEL ID");
			Ti.API.error("[SQL REST API] ERROR: MISSING MODEL ID");
			return;
		}
		params.url = params.url + '/' + model.id;
		logger(DEBUG, "delete options", params);

		apiCall(params, function(_response) {
			if (_response.success) {
				var data = parseJSON(_response, params.parentNode);
				resp = deleteSQL();
				_.isFunction(params.success) && params.success(resp);
			} else {
				// error or offline

				// save data locally when server returned an error
				if (!_response.localOnly && params.disableSaveDataLocallyOnServerError) {
					logger(DEBUG, "NOTICE: The data is not being deleted locally");
				} else {
					resp = deleteSQL();
				}

				if (_.isUndefined(_response.offline)) {
					//error
					_.isFunction(params.error) && params.error( params.returnErrorResponse ? _response : resp);
				} else {
					//offline - still a data success
					_.isFunction(params.success) && params.success(resp);
				}
			}
		});
		break;
	}

	/////////////////////////////////////////////
	//SQL INTERFACE
	/////////////////////////////////////////////
	function saveData(data) {
		if (!data && !isCollection) {
			data = model.toJSON();
		}
		if (!data) {
			// its empty
			return;
		}
		if (!_.isArray(data)) {// its a model
			if (!_.isUndefined(data[model.deletedAttribute]) && data[model.deletedAttribute] == true) {
				//delete item
				deleteSQL(data[model.idAttribute]);
			} else if (sqlFindItem(data[model.idAttribute]).length == 1) {
				//item exists - update it
				return updateSQL(data);
			} else {
				//write data to local sql
				return createSQL(data);
			}
		} else {//its an array of models
			var currentModels = sqlCurrentModels();
			for (var i in data) {
				if (!_.isUndefined(data[i][model.deletedAttribute]) && data[i][model.deletedAttribute] == true) {
					//delete item
					deleteSQL(data[i][model.idAttribute]);
				} else if (_.indexOf(currentModels, data[i][model.idAttribute]) != -1) {
					//item exists - update it
					updateSQL(data[i]);
				} else {
					//write data to local sql
					createSQL(data[i]);
				}
			}
		}
	}

	function createSQL(data) {
		var attrObj = {};
		logger(DEBUG, "createSQL data:", data);

		if (data) {
			attrObj = data;
		} else {
			if (!isCollection) {
				attrObj = model.toJSON();
			} else {
				Ti.API.error("[SQL REST API] Its a collection - error !");
			}
		}

		if (!attrObj[model.idAttribute]) {
			if (model.idAttribute === ALLOY_ID_DEFAULT) {
				// alloy-created GUID field
				attrObj.id = guid();
				attrObj[model.idAttribute] = attrObj.id;
			} else {
				// idAttribute not assigned by alloy. Leave it empty and
				// allow sqlite to process as null, which is the
				// expected value for an AUTOINCREMENT field.
				attrObj[model.idAttribute] = null;
			}
		}

		//validate the item
		if (params.useStrictValidation) {
			for (var c in columns) {
				if (c == model.idAttribute) {
					continue;
				}
				if (!_.contains(_.keys(attrObj), c)) {
					Ti.API.error("[SQL REST API] ITEM NOT VALID - REASON: " + c + " is not present");
					return;
				}
			}
		}

		// Create arrays for insert query
		var names = [],
		    values = [],
		    q = [];
		for (var k in columns) {
			names.push(k);
			if (_.isObject(attrObj[k])) {
				values.push(JSON.stringify(attrObj[k]));
			} else {
				values.push(attrObj[k]);
			}
			q.push('?');
		}

		// Last Modified logic
		if (params.lastModifiedColumn && _.isUndefined(params.disableLastModified)) {
			values[_.indexOf(names, params.lastModifiedColumn)] = params.lastModifiedDateFormat ? moment().format(params.lastModifiedDateFormat) : moment().lang('en').zone('GMT').format('ddd, D MMM YYYY HH:mm:ss ZZ');
		}

		// Assemble create query
		var sqlInsert = "INSERT INTO " + table + " (" + names.join(",") + ") VALUES (" + q.join(",") + ");";

		// execute the query and return the response
		db = Ti.Database.open(dbName);
		db.execute('BEGIN;');
		db.execute(sqlInsert, values);

		// get the last inserted id
		if (model.id === null) {
			var sqlId = "SELECT last_insert_rowid();";
			var rs = db.execute(sqlId);
			if (rs.isValidRow()) {
				model.id = rs.field(0);
				attrObj[model.idAttribute] = model.id;
			} else {
				Ti.API.warn('Unable to get ID from database for model: ' + model.toJSON());
			}
		}

		db.execute('COMMIT;');
		db.close();

		return attrObj;
	}

	function readSQL(data) {
		if (DEBUG) {
			Ti.API.debug("[SQL REST API] readSQL");
			logger(DEBUG, "\n******************************\nCollection total BEFORE read from db: " + model.length + " models\n******************************");
		}
		var sql = opts.query || 'SELECT * FROM ' + table;

		// we want the exact server response returned by the adapter
		if (params.returnExactServerResponse && data) {
			opts.sql = opts.sql || {};
			opts.sql.where = opts.sql.where || {};

			if (_.isEmpty(data)) {
				// No result
				opts.sql.where[model.idAttribute] = "1=2";
			} else {
				// Find all idAttribute in the server response
				var ids = [];
				_.each(data, function(element) {
					ids.push(element[model.idAttribute]);
				});
				// this will select IDs in the sql query
				opts.sql.where[model.idAttribute] = ids;
			}
		}

		// execute the select query
		db = Ti.Database.open(dbName);

		// run a specific sql query if defined
		if (opts.query) {
			if (opts.query.params) {
				var rs = db.execute(opts.query.sql, opts.query.params);
			} else {
				var rs = db.execute(opts.query.sql);
			}
		} else {
			//extend sql where with data
			if (opts.data) {
				opts.sql = opts.sql || {};
				opts.sql.where = opts.sql.where || {};
				_.extend(opts.sql.where, opts.data);
			}
			// build the sql query
			var sql = _buildQuery(table, opts.sql || opts);
			logger(DEBUG, "SQL QUERY: " + sql);

			var rs = db.execute(sql);
		}
		var len = 0,
		    values = [];

		// iterate through all queried rows
		while (rs.isValidRow()) {
			var o = {};
			var fc = 0;

			fc = _.isFunction(rs.fieldCount) ? rs.fieldCount() : rs.fieldCount;

			// create list of rows returned from query
			_.times(fc, function(c) {
				var fn = rs.fieldName(c);
				o[fn] = rs.fieldByName(fn);
			});
			values.push(o);

			// Only push models if its a collection
			// and not if we are using fetch({add:true})
			if (isCollection && !params.add) {
				//push the models
				var m = new model.config.Model(o);
				model.models.push(m);
			}
			len++;
			rs.next();
		}

		// close off db after read query
		rs.close();
		db.close();

		// shape response based on whether it's a model or collection
		model.length = len;

		logger(DEBUG, "\n******************************\n readSQL db read complete: " + len + " models \n******************************");
		resp = len === 1 ? values[0] : values;
		return resp;
	}

	function updateSQL(data) {
		var attrObj = {};

		logger(DEBUG, "updateSQL data: ", data);

		if (data) {
			attrObj = data;
		} else {
			if (!isCollection) {
				attrObj = model.toJSON();
			} else {
				Ti.API.error("Its a collection - error!");
			}
		}

		// Create arrays for insert query
		var names = [],
		    values = [],
		    q = [];
		for (var k in columns) {
			if (!_.isUndefined(attrObj[k])) {//only update those who are in the data
				names.push(k + '=?');
				if (_.isObject(attrObj[k])) {
					values.push(JSON.stringify(attrObj[k]));
				} else {
					values.push(attrObj[k]);
				}
				q.push('?');
			}
		}

		if (params.lastModifiedColumn && _.isUndefined(params.disableLastModified)) {
			values[_.indexOf(names, params.lastModifiedColumn + "=?")] = params.lastModifiedDateFormat ? moment().format(params.lastModifiedDateFormat) : moment().lang('en').zone('GMT').format('YYYY-MM-DD HH:mm:ss ZZ');
		}

		// compose the update query
		var sql = 'UPDATE ' + table + ' SET ' + names.join(',') + ' WHERE ' + model.idAttribute + '=?';
		values.push(attrObj[model.idAttribute]);

		logger(DEBUG, "updateSQL sql query: " + sql);
		logger(DEBUG, "updateSQL values: ", values);

		// execute the update
		db = Ti.Database.open(dbName);
		db.execute(sql, values);

		db.close();

		return attrObj;
	}

	function deleteSQL(id) {
		var sql = 'DELETE FROM ' + table + ' WHERE ' + model.idAttribute + '=?';
		// execute the delete
		db = Ti.Database.open(dbName);
		db.execute(sql, id || model.id);
		db.close();

		model.id = null;
		return model.toJSON();
	}

	function deleteAllSQL() {
		var sql = 'DELETE FROM ' + table;
		db = Ti.Database.open(dbName);
		db.execute(sql);
		db.close();
	}

	function deleteBasedOnSQL(obj) {
		if (!_.isObject(obj)) {
			Ti.API.error("[SQL REST API] deleteBasedOnSQL :: Error no object provided");
			return;
		}
		var sql = _buildQuery(table, obj, "DELETE");
		db = Ti.Database.open(dbName);
		db.execute(sql);
		db.close();
	}

	function sqlCurrentModels() {
		var sql = 'SELECT ' + model.idAttribute + ' FROM ' + table;
		db = Ti.Database.open(dbName);
		var rs = db.execute(sql);
		var output = [];
		while (rs.isValidRow()) {
			output.push(rs.fieldByName(model.idAttribute));
			rs.next();
		}
		rs.close();
		db.close();
		return output;
	}

	function sqlFindItem(_id) {
		if (_.isUndefined(_id)) {
			return [];
		}
		var sql = 'SELECT ' + model.idAttribute + ' FROM ' + table + ' WHERE ' + model.idAttribute + '=?';
		db = Ti.Database.open(dbName);
		var rs = db.execute(sql, _id);
		var output = [];
		while (rs.isValidRow()) {
			output.push(rs.fieldByName(model.idAttribute));
			rs.next();
		}
		rs.close();
		db.close();
		return output;
	}

	function sqlLastModifiedItem() {
		if (params.singleModelRequest || !isCollection) {
			//model
			var sql = 'SELECT ' + params.lastModifiedColumn + ' FROM ' + table + ' WHERE ' + params.lastModifiedColumn + ' IS NOT NULL AND ' + model.idAttribute + '=' + params.singleModelRequest + ' ORDER BY ' + params.lastModifiedColumn + ' DESC LIMIT 0,1';
		} else {
			//collection
			var sql = 'SELECT ' + params.lastModifiedColumn + ' FROM ' + table + ' WHERE ' + params.lastModifiedColumn + ' IS NOT NULL ORDER BY ' + params.lastModifiedColumn + ' DESC LIMIT 0,1';
		}

		db = Ti.Database.open(dbName);
		rs = db.execute(sql);
		var output = null;
		if (rs.isValidRow()) {
			output = rs.field(0);
		}
		rs.close();
		db.close();
		return output;
	}

	function parseJSON(_response, parentNode) {
		var data = _response.responseJSON;
		if (!_.isUndefined(parentNode)) {
			data = _.isFunction(parentNode) ? parentNode(data) : traverseProperties(data, parentNode);
		}
		logger(DEBUG, "server response: ", data);
		return data;
	}

}