1 /** 2 * @fileOverview 3 * @author <a href="https://www.labkey.org">LabKey</a> (<a href="mailto:info@labkey.com">info@labkey.com</a>) 4 * @license Copyright (c) 2012-2016 LabKey Corporation 5 * <p/> 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * <p/> 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * <p/> 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 * <p/> 18 */ 19 20 /** 21 * @private 22 * @class QueryKey The QueryKey is private -- use SchemaKey or FieldKey subclasses .fromString() or .fromParts() instead. 23 * 24 * @param parent 25 * @param name 26 */ 27 LABKEY.QueryKey = function (parent, name) 28 { 29 if (parent && !(parent instanceof parent.constructor)) 30 throw new Error("parent type not of same type as this"); 31 32 /** 33 * The parent FieldKey or null. 34 * @type LABKEY.FieldKey 35 */ 36 this.parent = parent; 37 /** 38 * The FieldKey name. 39 * @type string 40 */ 41 this.name = name; 42 }; 43 44 /** 45 * Get the unencoded QueryKey name. 46 * @returns {string} 47 */ 48 LABKEY.QueryKey.prototype.getName = function () 49 { 50 return this.name; 51 }; 52 53 /** 54 * Compares QueryKeys for equality. 55 * @param {QueryKey} other 56 * @returns {boolean} true if this QueryKey and the other are the same. 57 */ 58 LABKEY.QueryKey.prototype.equals = function(other) 59 { 60 return other != null && 61 this.constructor == other.constructor && 62 this.toString().toLowerCase() == other.toString().toLowerCase(); 63 }; 64 65 /** 66 * Returns an Array of unencoded QueryKey parts. 67 * @returns {Array} Array of unencoded QueryKey parts. 68 */ 69 LABKEY.QueryKey.prototype.getParts = function() 70 { 71 var ret = []; 72 if (this.parent) 73 { 74 ret = this.parent.getParts(); 75 } 76 ret.push(this.name); 77 return ret; 78 }; 79 80 /** 81 * @private 82 */ 83 LABKEY.QueryKey.prototype.toString = function(divider) 84 { 85 var parts = this.getParts(); 86 for (var i = 0; i < parts.length; i ++) 87 { 88 parts[i] = LABKEY.QueryKey.encodePart(parts[i]); 89 } 90 return parts.join(divider); 91 }; 92 93 /** 94 * Returns the encoded QueryKey string as the JSON representation of a QueryKey. 95 * Called by JSON.stringify(). 96 * @function 97 */ 98 LABKEY.QueryKey.prototype.toJSON = function() 99 { 100 return this.toString(); 101 }; 102 103 /** 104 * Returns a string suitable for display to the user. 105 * @function 106 * @returns {string} Unencoded display string. 107 */ 108 LABKEY.QueryKey.prototype.toDisplayString = function() 109 { 110 return this.getParts().join('.'); 111 }; 112 113 /** 114 * @private 115 * @returns {string} Returns a dotted string with any SQL identifiers quoted. 116 */ 117 LABKEY.QueryKey.prototype.toSQLString = function() 118 { 119 var parts = this.getParts(); 120 for (var i = 0; i < parts.length; i ++) 121 { 122 if (LABKEY.QueryKey.needsQuotes(parts[i])) 123 { 124 parts[i] = LABKEY.QueryKey.quote(parts[i]); 125 } 126 } 127 return parts.join('.'); 128 }; 129 130 /** 131 * Use QueryKey encoding to encode a single part. 132 * This matches org.labkey.api.query.QueryKey.encodePart() in the Java API. 133 * @private 134 * @static 135 * @returns {string} 136 */ 137 LABKEY.QueryKey.encodePart = function(s) 138 { 139 return s.replace(/\$/g, "$D").replace(/\//g, "$S").replace(/\&/g, "$A").replace(/\}/g, "$B").replace(/\~/g, "$T").replace(/\,/g, "$C").replace(/\./g, "$P"); 140 }; 141 142 /** 143 * Use QueryKey encoding to decode a single part. 144 * This matches org.labkey.api.query.QueryKey.decodePart() in the Java API. 145 * @private 146 * @static 147 * @returns {string} 148 */ 149 LABKEY.QueryKey.decodePart = function(s) 150 { 151 return s.replace(/\$P/g, '.').replace(/\$C/g, ',').replace(/\$T/g, '~').replace(/\$B/g, '}').replace(/\$A/g, '&').replace(/\$S/g, '/').replace(/\$D/g, '$'); 152 }; 153 154 /** 155 * Returns true if the part needs to be SQL quoted. 156 * @private 157 * @static 158 * @returns {Boolean} 159 */ 160 LABKEY.QueryKey.needsQuotes = function(s) 161 { 162 if (!s.match(/^[a-zA-Z][_\$a-zA-Z0-9]*$/)) 163 return true; 164 if (s.match(/^(all|any|and|as|asc|avg|between|class|count|delete|desc|distinct|elements|escape|exists|false|fetch|from|full|group|having|in|indices|inner|insert|into|is|join|left|like|limit|max|min|new|not|null|or|order|outer|right|select|set|some|sum|true|union|update|user|versioned|where|case|end|else|then|when|on|both|empty|leading|member|of|trailing)$/i)) 165 return true; 166 return false; 167 }; 168 169 /** 170 * SQL quotes a bare string. 171 * @private 172 * @static 173 * @param {string} s String to be quoted. 174 * @returns {string} Quoted string. 175 */ 176 LABKEY.QueryKey.quote = function(s) 177 { 178 return '"' + s.replace(/\"/g, '""') + '"'; 179 }; 180 181 182 /** 183 * @class SchemaKey identifies a schema path. 184 * Use {@link LABKEY.SchemaKey.fromString()} or {@link LABKEY.SchemaKey.fromParts()} to create new SchemaKey. 185 * 186 * @constructor 187 * @extends LABKEY.QueryKey 188 * @param {LABKEY.SchemaKey} parent The parent SchemaKey or null. 189 * @param {string} name The FieldKey's unencoded name. 190 */ 191 LABKEY.SchemaKey = function (parent, name) 192 { 193 LABKEY.QueryKey.call(this, parent, name); 194 }; 195 LABKEY.SchemaKey.prototype = new LABKEY.QueryKey; 196 LABKEY.SchemaKey.prototype.constructor = LABKEY.SchemaKey; 197 198 /** 199 * Returns an encoded SchemaKey string suitable for sending to the server. 200 * @function 201 * @returns {string} Encoded SchemaKey string. 202 */ 203 LABKEY.SchemaKey.prototype.toString = function() 204 { 205 return LABKEY.QueryKey.prototype.toString.call(this, '.'); 206 }; 207 208 /** 209 * Create new SchemaKey from a SchemaKey encoded string with parts separated by '.' characters. 210 * 211 * @static 212 * @param {string} str SchemaKey string with SchemaKey encoded parts separated by '.' characters. 213 * @returns {LABKEY.SchemaKey} 214 */ 215 LABKEY.SchemaKey.fromString = function(str) 216 { 217 var rgStr = str.split('.'); 218 var ret = null; 219 for (var i = 0; i < rgStr.length; i ++) 220 { 221 ret = new LABKEY.SchemaKey(ret, LABKEY.QueryKey.decodePart(rgStr[i])); 222 } 223 return ret; 224 225 }; 226 227 /** 228 * Create new SchemaKey from an Array of unencoded SchemaKey string parts. 229 * 230 * @static 231 * @param {Array} parts Array of unencoded SchemaKey string parts. 232 * @returns {LABKEY.FieldKey} 233 */ 234 LABKEY.SchemaKey.fromParts = function(parts) 235 { 236 var ret = null; 237 for (var i = 0; i < arguments.length; i ++) 238 { 239 var arg = arguments[i]; 240 if (typeof arg === 'string') 241 { 242 ret = new LABKEY.SchemaKey(ret, arg); 243 } 244 else if (arg && arg.length) 245 { 246 for (var j = 0; j < arg.length; j++) 247 { 248 ret = new LABKEY.SchemaKey(ret, arg[j]); 249 } 250 } 251 else 252 { 253 throw "Illegal argument to fromParts: " + arg; 254 } 255 } 256 return ret; 257 }; 258 259 260 /** 261 * The FieldKey constructor is private - use fromString() or fromParts() instead. 262 * 263 * @class FieldKey identifies a column from a table or query. 264 * Use {@link LABKEY.FieldKey.fromString()} or {@link LABKEY.FieldKey.fromParts()} to create new FieldKeys. 265 * <p> 266 * Example: Create a new FieldKey for column "C" from foreign key column "A,B". 267 <pre name="code"> 268 var fieldKey = LABKEY.FieldKey.fromParts(["A,B", "C"]); 269 270 fieldKey.name; 271 // => "C" 272 fieldKey.parent; 273 // => LABKEY.FieldKey for "A,B" 274 fieldKey.toDisplayString(); 275 // => "A,B/C" 276 fieldKey.toString(); 277 // => "A$CB/C" 278 fieldKey.equals(LABKEY.FieldKey.fromString(fieldKey.toString())); 279 // => true 280 </pre> 281 * 282 * @constructor 283 * @extends LABKEY.SchemaKey 284 * @param {LABKEY.FieldKey} parent The parent FieldKey or null. 285 * @param {string} name The FieldKey's unencoded name. 286 */ 287 LABKEY.FieldKey = function (parent, name) 288 { 289 LABKEY.QueryKey.call(this, parent, name); 290 }; 291 LABKEY.FieldKey.prototype = new LABKEY.QueryKey; 292 LABKEY.FieldKey.prototype.constructor = LABKEY.FieldKey; 293 294 /** 295 * Returns an encoded FieldKey string suitable for sending to the server. 296 * @function 297 * @returns {string} Encoded FieldKey string. 298 */ 299 LABKEY.FieldKey.prototype.toString = function() 300 { 301 return LABKEY.QueryKey.prototype.toString.call(this, '/'); 302 }; 303 304 305 /** 306 * Create new FieldKey from a FieldKey encoded string with parts separated by '/' characters. 307 * 308 * @static 309 * @param {string} str FieldKey string with FieldKey encoded parts separated by '/' characters. 310 * @returns {LABKEY.FieldKey} 311 */ 312 LABKEY.FieldKey.fromString = function(str) 313 { 314 var rgStr = str.split('/'); 315 var ret = null; 316 for (var i = 0; i < rgStr.length; i ++) 317 { 318 ret = new LABKEY.FieldKey(ret, LABKEY.QueryKey.decodePart(rgStr[i])); 319 } 320 return ret; 321 322 }; 323 324 /** 325 * Create new FieldKey from an Array of unencoded FieldKey string parts. 326 * 327 * @static 328 * @param {Array} parts Array of unencoded FieldKey string parts. 329 * @returns {LABKEY.FieldKey} 330 */ 331 LABKEY.FieldKey.fromParts = function() 332 { 333 var ret = null; 334 for (var i = 0; i < arguments.length; i ++) 335 { 336 var arg = arguments[i]; 337 if (typeof arg === 'string') 338 { 339 ret = new LABKEY.FieldKey(ret, arg); 340 } 341 else if (arg && arg.length) 342 { 343 for (var j = 0; j < arg.length; j++) 344 { 345 ret = new LABKEY.FieldKey(ret, arg[j]); 346 } 347 } 348 else 349 { 350 throw "Illegal argument to fromParts: " + arg; 351 } 352 } 353 return ret; 354 }; 355 356 /** docs for inherited methods defined in private class QueryKey */ 357 358 /** 359 * Get the unencoded QueryKey name. 360 * @memberOf LABKEY.SchemaKey 361 * @function 362 * @name getName 363 * @returns {string} 364 */ 365 366 /** 367 * Compares QueryKeys for equality. 368 * @memberOf LABKEY.SchemaKey 369 * @function 370 * @name equals 371 * @param {QueryKey} other 372 * @returns {boolean} true if this QueryKey and the other are the same. 373 */ 374 375 /** 376 * Returns an Array of unencoded QueryKey parts. 377 * @memberOf LABKEY.SchemaKey 378 * @function 379 * @name getParts 380 * @returns {Array} Array of unencoded QueryKey parts. 381 */ 382 383 /** 384 * Returns the encoded QueryKey string as the JSON representation of a QueryKey. 385 * Called by JSON.stringify(). 386 * @memberOf LABKEY.SchemaKey 387 * @function 388 * @name toJSON 389 */ 390 391 /** 392 * Returns a string suitable for display to the user. 393 * @memberOf LABKEY.SchemaKey 394 * @function 395 * @name toDisplayString 396 */ 397 398 /** 399 * Get the unencoded QueryKey name. 400 * @memberOf LABKEY.FieldKey 401 * @function 402 * @name getName 403 * @returns {string} 404 */ 405 406 /** 407 * Compares QueryKeys for equality. 408 * @memberOf LABKEY.FieldKey 409 * @function 410 * @name equals 411 * @param {QueryKey} other 412 * @returns {boolean} true if this QueryKey and the other are the same. 413 */ 414 415 /** 416 * Returns an Array of unencoded QueryKey parts. 417 * @memberOf LABKEY.FieldKey 418 * @function 419 * @name getParts 420 * @returns {Array} Array of unencoded QueryKey parts. 421 */ 422 423 /** 424 * Returns the encoded QueryKey string as the JSON representation of a QueryKey. 425 * Called by JSON.stringify(). 426 * @memberOf LABKEY.FieldKey 427 * @function 428 * @name toJSON 429 */ 430 431 /** 432 * Returns a string suitable for display to the user. 433 * @memberOf LABKEY.FieldKey 434 * @function 435 * @name toDisplayString 436 */ 437