• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.softmotions.ejdb2;
2 
3 import java.io.ByteArrayOutputStream;
4 import java.io.OutputStream;
5 import java.io.UnsupportedEncodingException;
6 import java.lang.ref.ReferenceQueue;
7 import java.lang.ref.WeakReference;
8 import java.util.ArrayList;
9 import java.util.List;
10 import java.util.Map;
11 import java.util.StringJoiner;
12 import java.util.concurrent.ConcurrentHashMap;
13 
14 /**
15  * EJDB2 Query specification.
16  * <p>
17  * Query can be reused multiple times with various placeholder parameters. See
18  * JQL specification:
19  * https://github.com/Softmotions/ejdb/blob/master/README.md#jql
20  * <p>
21  * Memory resources used by JQL instance must be released explicitly by
22  * {@link JQL#close()}.
23  * <p>
24  * <strong>Note:</strong> If user did not close instance explicitly it will be
25  * freed anyway once jql object will be garbage collected.
26  * <p>
27  * Typical usage:
28  *
29  * <pre>
30  * {@code
31  *    try (JQL q = db.createQuery("/[foo=:val]", "mycoll")
32  *                   .setString("val", "bar")) {
33  *       q.execute((docId, doc) -> {
34  *         System.out.println(String.format("Found %d %s", docId, doc));
35  *         return 1;
36  *       });
37  *    }
38  * }
39  * </pre>
40  */
41 public final class JQL implements AutoCloseable {
42 
43   private static final ReferenceQueue<JQL> refQueue = new ReferenceQueue<>();
44 
45   @SuppressWarnings("StaticCollection")
46   private static final Map<Long, Reference> refs = new ConcurrentHashMap<Long, Reference>();
47 
48   private static final Thread cleanupThread = new Thread(() -> {
49     while (true) {
50       try {
51         ((Reference) refQueue.remove()).cleanup();
52       } catch (InterruptedException ignored) {
53       }
54     }
55   });
56 
57   static {
58     cleanupThread.setDaemon(true);
cleanupThread.start()59     cleanupThread.start();
60   }
61 
62   private final EJDB2 db;
63 
64   private final String query;
65 
66   private String collection;
67 
68   private long skip;
69 
70   private long limit;
71 
72   private long _handle;
73 
74   private ByteArrayOutputStream explain;
75 
76   /**
77    * Owner database instance
78    */
getDb()79   public EJDB2 getDb() {
80     return db;
81   }
82 
83   /**
84    * Query specification used to construct this query object.
85    */
getQuery()86   public String getQuery() {
87     return query;
88   }
89 
90   /**
91    * Collection name used for this query
92    */
getCollection()93   public String getCollection() {
94     return collection;
95   }
96 
setCollection(String collection)97   public JQL setCollection(String collection) {
98     this.collection = collection;
99     return this;
100   }
101 
102   /**
103    * Turn on collecting of query execution log
104    *
105    * @see #getExplainLog()
106    */
withExplain()107   public JQL withExplain() {
108     explain = new ByteArrayOutputStream();
109     return this;
110   }
111 
112   /**
113    * Turn off collecting of query execution log
114    *
115    * @see #getExplainLog()
116    */
withNoExplain()117   public JQL withNoExplain() {
118     explain = null;
119     return this;
120   }
121 
getExplainLog()122   public String getExplainLog() {
123     try {
124       return explain != null ? explain.toString("UTF-8") : null;
125     } catch (UnsupportedEncodingException ignored) {
126       return null;
127     }
128   }
129 
130   /**
131    * Number of records to skip. This parameter takes precedence over {@code skip}
132    * encoded in query spec.
133    *
134    * @return
135    */
setSkip(long skip)136   public JQL setSkip(long skip) {
137     this.skip = skip;
138     return this;
139   }
140 
getSkip()141   public long getSkip() {
142     return skip > 0 ? skip : _get_skip();
143   }
144 
145   /**
146    * Maximum number of records to retrive. This parameter takes precedence over
147    * {@code limit} encoded in query spec.
148    */
setLimit(long limit)149   public JQL setLimit(long limit) {
150     this.limit = limit;
151     return this;
152   }
153 
getLimit()154   public long getLimit() {
155     return limit > 0 ? limit : _get_limit();
156   }
157 
158   /**
159    * Set positional string parameter starting for {@code 0} index.
160    * <p>
161    * Example:
162    *
163    * <pre>
164    * {@code
165    *  db.createQuery("/[foo=:?]", "mycoll").setString(0, "zaz")
166    * }
167    * </pre>
168    *
169    * @param  pos            Zero based positional index
170    * @param  val            Value to set
171    * @return
172    * @throws EJDB2Exception
173    */
setString(int pos, String val)174   public JQL setString(int pos, String val) throws EJDB2Exception {
175     _set_string(pos, null, val, 0);
176     return this;
177   }
178 
179   /**
180    * Set string parameter placeholder in query spec.
181    * <p>
182    * Example:
183    *
184    * <pre>
185    * {@code
186    *  db.createQuery("/[foo=:val]", "mycoll").setString("val", "zaz");
187    * }
188    * </pre>
189    *
190    * @param  placeholder    Placeholder name
191    * @param  val            Value to set
192    * @return
193    * @throws EJDB2Exception
194    */
setString(String placeholder, String val)195   public JQL setString(String placeholder, String val) throws EJDB2Exception {
196     _set_string(0, placeholder, val, 0);
197     return this;
198   }
199 
setLong(int pos, long val)200   public JQL setLong(int pos, long val) throws EJDB2Exception {
201     _set_long(pos, null, val);
202     return this;
203   }
204 
setLong(String placeholder, long val)205   public JQL setLong(String placeholder, long val) throws EJDB2Exception {
206     _set_long(0, placeholder, val);
207     return this;
208   }
209 
setJSON(int pos, String json)210   public JQL setJSON(int pos, String json) throws EJDB2Exception {
211     _set_string(pos, null, json, 1);
212     return this;
213   }
214 
setJSON(int pos, JSON json)215   public JQL setJSON(int pos, JSON json) throws EJDB2Exception {
216     _set_string(pos, null, json.toString(), 1);
217     return this;
218   }
219 
setJSON(String placeholder, String json)220   public JQL setJSON(String placeholder, String json) throws EJDB2Exception {
221     _set_string(0, placeholder, json, 1);
222     return this;
223   }
224 
setJSON(String placeholder, JSON json)225   public JQL setJSON(String placeholder, JSON json) throws EJDB2Exception {
226     _set_string(0, placeholder, json.toString(), 1);
227     return this;
228   }
229 
setRegexp(int pos, String regexp)230   public JQL setRegexp(int pos, String regexp) throws EJDB2Exception {
231     _set_string(pos, null, regexp, 2);
232     return this;
233   }
234 
setRegexp(String placeholder, String regexp)235   public JQL setRegexp(String placeholder, String regexp) throws EJDB2Exception {
236     _set_string(0, placeholder, regexp, 2);
237     return this;
238   }
239 
setDouble(int pos, double val)240   public JQL setDouble(int pos, double val) throws EJDB2Exception {
241     _set_double(pos, null, val);
242     return this;
243   }
244 
setDouble(String placeholder, double val)245   public JQL setDouble(String placeholder, double val) throws EJDB2Exception {
246     _set_double(0, placeholder, val);
247     return this;
248   }
249 
setBoolean(int pos, boolean val)250   public JQL setBoolean(int pos, boolean val) throws EJDB2Exception {
251     _set_boolean(pos, null, val);
252     return this;
253   }
254 
setBoolean(String placeholder, boolean val)255   public JQL setBoolean(String placeholder, boolean val) throws EJDB2Exception {
256     _set_boolean(0, placeholder, val);
257     return this;
258   }
259 
setNull(int pos)260   public JQL setNull(int pos) throws EJDB2Exception {
261     _set_null(pos, null);
262     return this;
263   }
264 
setNull(String placeholder)265   public JQL setNull(String placeholder) throws EJDB2Exception {
266     _set_null(0, placeholder);
267     return this;
268   }
269 
270   /**
271    * Execute query and handle record {@link EJDB2Document} values by provided
272    * {@code cb}
273    *
274    * @param  cb             Optional callback
275    * @throws EJDB2Exception
276    */
execute(EJDB2DocumentCallback cb)277   public void execute(EJDB2DocumentCallback cb) throws EJDB2Exception {
278     if (explain != null) {
279       explain.reset();
280     }
281     if (cb != null) {
282       _execute(db, (id, sv) -> cb.onDocument(new EJDB2Document(id, sv)), explain);
283     } else {
284       _execute(db, null, explain);
285     }
286   }
287 
288   /**
289    * Execute query without result set callback.
290    *
291    * @throws EJDB2Exception
292    */
execute()293   public void execute() throws EJDB2Exception {
294     execute(null);
295   }
296 
executeRaw(JQLCallback cb)297   public void executeRaw(JQLCallback cb) throws EJDB2Exception {
298     if (explain != null) {
299       explain.reset();
300     }
301     if (cb != null) {
302       _execute(db, (id, sv) -> cb.onRecord(id, sv), explain);
303     } else {
304       _execute(db, null, explain);
305     }
306   }
307 
list()308   public List<EJDB2Document> list() throws EJDB2Exception {
309     List<EJDB2Document> list = new ArrayList<>();
310     execute((doc) -> {
311       list.add(doc);
312       return 1;
313     });
314     return list;
315   }
316 
first()317   public EJDB2Document first() {
318     final EJDB2Document[] v = { null };
319     if (explain != null) {
320       explain.reset();
321     }
322     _execute(db, (id, json) -> {
323       v[0] = new EJDB2Document(id, json);
324       return 0;
325     }, explain);
326     return v[0];
327   }
328 
329   /**
330    * Get first document body as JSON string or null.
331    */
firstValue()332   public String firstValue() {
333     final String[] v = { null };
334     if (explain != null) {
335       explain.reset();
336     }
337     _execute(db, (id, json) -> {
338       v[0] = json;
339       return 0;
340     }, explain);
341     return v[0];
342   }
343 
344   /**
345    * Get first document id ot null
346    */
firstId()347   public Long firstId() {
348     final Long[] v = { null };
349     if (explain != null) {
350       explain.reset();
351     }
352     _execute(db, (id, json) -> {
353       v[0] = id;
354       return 0;
355     }, explain);
356     return v[0];
357   }
358 
359   /**
360    * Execute scalar query.
361    * <p>
362    * Example:
363    *
364    * <pre>
365    * long count = db.createQuery("@mycoll/* | count").executeScalarInt();
366    * </pre>
367    */
executeScalarInt()368   public long executeScalarInt() {
369     if (explain != null) {
370       explain.reset();
371     }
372     return _execute_scalar_long(db, explain);
373   }
374 
375   /**
376    * Reset data stored in positional placeholderss
377    */
reset()378   public void reset() {
379     if (explain != null) {
380       explain.reset();
381     }
382     _reset();
383   }
384 
385   /**
386    * Close query instance releasing memory resources
387    */
388   @Override
close()389   public void close() throws Exception {
390     Reference ref = refs.get(_handle);
391     if (ref != null) {
392       ref.enqueue();
393     } else {
394       long h = _handle;
395       if (h != 0) {
396         _destroy(h);
397       }
398     }
399   }
400 
JQL(EJDB2 db, String query, String collection)401   JQL(EJDB2 db, String query, String collection) throws EJDB2Exception {
402     this.db = db;
403     this.query = query;
404     this.collection = collection;
405     _init(db, query, collection);
406     // noinspection InstanceVariableUsedBeforeInitialized
407     refs.put(_handle, new Reference(this, refQueue));
408   }
409 
410   @Override
toString()411   public String toString() {
412     return new StringJoiner(", ", JQL.class.getSimpleName() + "[", "]")
413       .add("query=" + query)
414       .add("collection=" + collection)
415       .toString();
416   }
417 
418   private static class Reference extends WeakReference<JQL> {
419     private long handle;
420 
Reference(JQL jql, ReferenceQueue<JQL> rq)421     Reference(JQL jql, ReferenceQueue<JQL> rq) {
422       super(jql, rq);
423       handle = jql._handle;
424     }
425 
cleanup()426     void cleanup() {
427       long h = handle;
428       handle = 0L;
429       if (h != 0) {
430         refs.remove(h);
431         _destroy(h);
432       }
433     }
434   }
435 
_destroy(long handle)436   private static native void _destroy(long handle);
437 
_init(EJDB2 db, String query, String collection)438   private native void _init(EJDB2 db, String query, String collection);
439 
_execute(EJDB2 db, JQLCallback cb, OutputStream explainLog)440   private native void _execute(EJDB2 db, JQLCallback cb, OutputStream explainLog);
441 
_execute_scalar_long(EJDB2 db, OutputStream explainLog)442   private native long _execute_scalar_long(EJDB2 db, OutputStream explainLog);
443 
_reset()444   private native void _reset();
445 
_get_limit()446   private native long _get_limit();
447 
_get_skip()448   private native long _get_skip();
449 
_set_string(int pos, String placeholder, String val, int type)450   private native void _set_string(int pos, String placeholder, String val, int type);
451 
_set_long(int pos, String placeholder, long val)452   private native void _set_long(int pos, String placeholder, long val);
453 
_set_double(int pos, String placeholder, double val)454   private native void _set_double(int pos, String placeholder, double val);
455 
_set_boolean(int pos, String placeholder, boolean val)456   private native void _set_boolean(int pos, String placeholder, boolean val);
457 
_set_null(int pos, String placeholder)458   private native void _set_null(int pos, String placeholder);
459 }