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