`.
96 |
97 | If you are working with an instance of Couchbase on your local machine, use the string `jdbc:couchbase://localhost:8093`.
98 |
99 | If you are working with a Couchbase cluster, the host should be the name or ip address of a Couchbase node running the Query service.
100 | The driver will then distribute the queries around the nodes of the cluster in round-robin fashion. You can check which nodes are
101 | running the Query service on the "Server Nodes" tab of the Couchbase Admin Console. The "Services" column shows which nodes are
102 | running which service. The port is the port of the query service; 8093 by default.
103 |
104 | Couchbase supports access over SSL-protected connections, but only Enterprise Edition supports this, not Community Edition.
105 | To use SSL, connect on the 18093 port rather than the standard 8093 port. For a more detailed example of how to use SSL, consult
106 | the SSL connection test at *src/test/java/com/couchbase/jdbc/SSLConnectionTest.java*.
107 |
108 | ## Building the Driver from Source
109 |
110 | You need to have Git, Maven 3 and Java 8 installed on your machine.
111 |
112 | Run the following commands:
113 |
114 | git clone https://github.com/jdbc-json/jdbc-cb
115 | cd jdbc-cb
116 | mvn -Dmaven.test.skip=true package
117 |
118 | The JAR file will be in the */target* directory.
119 |
120 | By default, `mvn package` runs the unit tests before creating the JAR.
121 | These directions specifically omit running the unit tests, because the tests require additional setup, as explained in the next section.
122 | After the setup is complete, you can run `mvn package` without the extra flag.
123 |
124 | ## Running the Unit Tests
125 |
126 | (Assuming you have downloaded and built the driver from source, as described above.)
127 |
128 | The unit tests assume an instance of Couchbase Enterprise Edition is set up and accessible on
129 | the local machine. If the Couchbase instance is Community Edition, the SSLConnectionTest will fail because
130 | Community Edition instances are not accessible over SSL.
131 | The administrator and password should be "Administrator" and "password",
132 | respectively. The *beer-sample* and *default* data buckets
133 | should be present. The *default* bucket is always present; *beer-sample* is created on request at installation time.
134 |
135 | The beer-sample and default buckets must be indexed for the tests to run correctly.
136 | You can index them by running these two commands in the CBQ:
137 |
138 | create primary index on default;
139 | create primary index on `beer-sample`;
140 |
141 | Note the back-ticks in the second line.
142 |
143 | Run all the tests with this command:
144 |
145 | mvn test
146 |
147 | Run a specific test with a command like this:
148 |
149 | mvn test -Dtest=com.couchbase.jdbc.PreparedStatementTest
150 |
--------------------------------------------------------------------------------
/src/main/java/com/couchbase/jdbc/CBParameterMetaData.java:
--------------------------------------------------------------------------------
1 | /*
2 | * // Copyright (c) 2015 Couchbase, Inc.
3 | * // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
4 | * // except in compliance with the License. You may obtain a copy of the License at
5 | * // http://www.apache.org/licenses/LICENSE-2.0
6 | * // Unless required by applicable law or agreed to in writing, software distributed under the
7 | * // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
8 | * // either express or implied. See the License for the specific language governing permissions
9 | * // and limitations under the License.
10 | */
11 |
12 | package com.couchbase.jdbc;
13 |
14 |
15 | import java.sql.ParameterMetaData;
16 | import java.sql.SQLException;
17 |
18 | /**
19 | * Created by davec on 2015-07-28.
20 | */
21 | public class CBParameterMetaData implements ParameterMetaData
22 | {
23 | final CBPreparedResult preparedResult;
24 |
25 | public CBParameterMetaData(CBPreparedResult preparedResult)
26 | {
27 | this.preparedResult = preparedResult;
28 | }
29 | /**
30 | * Retrieves the number of parameters in the PreparedStatement
31 | * object for which this ParameterMetaData object contains
32 | * information.
33 | *
34 | * @return the number of parameters
35 | * @throws SQLException if a database access error occurs
36 | * @since 1.4
37 | */
38 | @Override
39 | public int getParameterCount() throws SQLException
40 | {
41 | return 0;
42 | }
43 |
44 | /**
45 | * Retrieves whether null values are allowed in the designated parameter.
46 | *
47 | * @param param the first parameter is 1, the second is 2, ...
48 | * @return the nullability status of the given parameter; one of
49 | * ParameterMetaData.parameterNoNulls,
50 | * ParameterMetaData.parameterNullable, or
51 | * ParameterMetaData.parameterNullableUnknown
52 | * @throws SQLException if a database access error occurs
53 | * @since 1.4
54 | */
55 | @Override
56 | public int isNullable(int param) throws SQLException
57 | {
58 | return parameterNullable;
59 | }
60 |
61 | /**
62 | * Retrieves whether values for the designated parameter can be signed numbers.
63 | *
64 | * @param param the first parameter is 1, the second is 2, ...
65 | * @return true if so; false otherwise
66 | * @throws SQLException if a database access error occurs
67 | * @since 1.4
68 | */
69 | @Override
70 | public boolean isSigned(int param) throws SQLException
71 | {
72 | return false;
73 | }
74 |
75 | /**
76 | * Retrieves the designated parameter's specified column size.
77 | *
78 | * The returned value represents the maximum column size for the given parameter.
79 | * For numeric data, this is the maximum precision. For character data, this is the length in characters.
80 | * For datetime datatypes, this is the length in characters of the String representation (assuming the
81 | * maximum allowed precision of the fractional seconds component). For binary data, this is the length in bytes. For the ROWID datatype,
82 | * this is the length in bytes. 0 is returned for data types where the
83 | * column size is not applicable.
84 | *
85 | * @param param the first parameter is 1, the second is 2, ...
86 | * @return precision
87 | * @throws SQLException if a database access error occurs
88 | * @since 1.4
89 | */
90 | @Override
91 | public int getPrecision(int param) throws SQLException
92 | {
93 | return 0;
94 | }
95 |
96 | /**
97 | * Retrieves the designated parameter's number of digits to right of the decimal point.
98 | * 0 is returned for data types where the scale is not applicable.
99 | *
100 | * @param param the first parameter is 1, the second is 2, ...
101 | * @return scale
102 | * @throws SQLException if a database access error occurs
103 | * @since 1.4
104 | */
105 | @Override
106 | public int getScale(int param) throws SQLException
107 | {
108 | return 0;
109 | }
110 |
111 | /**
112 | * Retrieves the designated parameter's SQL type.
113 | *
114 | * @param param the first parameter is 1, the second is 2, ...
115 | * @return SQL type from java.sql.Types
116 | * @throws SQLException if a database access error occurs
117 | * @see java.sql.Types
118 | * @since 1.4
119 | */
120 | @Override
121 | public int getParameterType(int param) throws SQLException
122 | {
123 | return 0;
124 | }
125 |
126 | /**
127 | * Retrieves the designated parameter's database-specific type name.
128 | *
129 | * @param param the first parameter is 1, the second is 2, ...
130 | * @return type the name used by the database. If the parameter type is
131 | * a user-defined type, then a fully-qualified type name is returned.
132 | * @throws SQLException if a database access error occurs
133 | * @since 1.4
134 | */
135 | @Override
136 | public String getParameterTypeName(int param) throws SQLException
137 | {
138 | return null;
139 | }
140 |
141 | /**
142 | * Retrieves the fully-qualified name of the Java class whose instances
143 | * should be passed to the method PreparedStatement.setObject.
144 | *
145 | * @param param the first parameter is 1, the second is 2, ...
146 | * @return the fully-qualified name of the class in the Java programming
147 | * language that would be used by the method
148 | * PreparedStatement.setObject to set the value
149 | * in the specified parameter. This is the class name used
150 | * for custom mapping.
151 | * @throws SQLException if a database access error occurs
152 | * @since 1.4
153 | */
154 | @Override
155 | public String getParameterClassName(int param) throws SQLException
156 | {
157 | return null;
158 | }
159 |
160 | /**
161 | * Retrieves the designated parameter's mode.
162 | *
163 | * @param param the first parameter is 1, the second is 2, ...
164 | * @return mode of the parameter; one of
165 | * ParameterMetaData.parameterModeIn,
166 | * ParameterMetaData.parameterModeOut, or
167 | * ParameterMetaData.parameterModeInOut
168 | * ParameterMetaData.parameterModeUnknown.
169 | * @throws SQLException if a database access error occurs
170 | * @since 1.4
171 | */
172 | @Override
173 | public int getParameterMode(int param) throws SQLException
174 | {
175 | return parameterModeIn;
176 | }
177 |
178 | /**
179 | * Returns an object that implements the given interface to allow access to
180 | * non-standard methods, or standard methods not exposed by the proxy.
181 | *
182 | * If the receiver implements the interface then the result is the receiver
183 | * or a proxy for the receiver. If the receiver is a wrapper
184 | * and the wrapped object implements the interface then the result is the
185 | * wrapped object or a proxy for the wrapped object. Otherwise return the
186 | * the result of calling unwrap recursively on the wrapped object
187 | * or a proxy for that result. If the receiver is not a
188 | * wrapper and does not implement the interface, then an SQLException is thrown.
189 | *
190 | * @param iface A Class defining an interface that the result must implement.
191 | * @return an object that implements the interface. May be a proxy for the actual implementing object.
192 | * @throws SQLException If no object found that implements the interface
193 | * @since 1.6
194 | */
195 | @Override
196 | public T unwrap(Class iface) throws SQLException
197 | {
198 | if (iface.isAssignableFrom(getClass()))
199 | {
200 | return iface.cast(this);
201 | }
202 | throw new SQLException("Cannot unwrap to " + iface.getName());
203 | }
204 |
205 | /**
206 | * Returns true if this either implements the interface argument or is directly or indirectly a wrapper
207 | * for an object that does. Returns false otherwise. If this implements the interface then return true,
208 | * else if this is a wrapper then return the result of recursively calling isWrapperFor on the wrapped
209 | * object. If this does not implement the interface and is not a wrapper, return false.
210 | * This method should be implemented as a low-cost operation compared to unwrap so that
211 | * callers can use this method to avoid expensive unwrap calls that may fail. If this method
212 | * returns true then calling unwrap with the same argument should succeed.
213 | *
214 | * @param iface a Class defining an interface.
215 | * @return true if this implements the interface or directly or indirectly wraps an object that does.
216 | * @throws SQLException if an error occurs while determining whether this is a wrapper
217 | * for an object with the given interface.
218 | * @since 1.6
219 | */
220 | @Override
221 | public boolean isWrapperFor(Class> iface) throws SQLException
222 | {
223 | return iface.isAssignableFrom(getClass());
224 | }
225 | }
226 |
--------------------------------------------------------------------------------
/src/main/java/com/couchbase/jdbc/util/TimestampUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * // Copyright (c) 2015 Couchbase, Inc.
3 | * // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
4 | * // except in compliance with the License. You may obtain a copy of the License at
5 | * // http://www.apache.org/licenses/LICENSE-2.0
6 | * // Unless required by applicable law or agreed to in writing, software distributed under the
7 | * // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
8 | * // either express or implied. See the License for the specific language governing permissions
9 | * // and limitations under the License.
10 | */
11 |
12 | package com.couchbase.jdbc.util;
13 |
14 | import java.sql.Date;
15 | import java.sql.Time;
16 | import java.sql.Timestamp;
17 | import java.text.SimpleDateFormat;
18 | import java.util.Calendar;
19 | import java.util.GregorianCalendar;
20 | import java.util.TimeZone;
21 |
22 | /**
23 | * Created by davec on 2015-08-03.
24 | */
25 | public class TimestampUtils
26 | {
27 | private StringBuffer sbuf = new StringBuffer();
28 | private Calendar defaultCal = new GregorianCalendar();
29 | private final TimeZone defaultTz = defaultCal.getTimeZone();
30 |
31 | public synchronized String toString(Calendar cal, Timestamp x) {
32 | if (cal == null)
33 | cal = defaultCal;
34 |
35 | cal.setTime(x);
36 | sbuf.setLength(0);
37 |
38 | appendDate(sbuf, cal);
39 | sbuf.append(' ');
40 | appendTime(sbuf, cal, x.getNanos());
41 | appendTimeZone(sbuf, cal);
42 | appendEra(sbuf, cal);
43 |
44 | return sbuf.toString();
45 | }
46 |
47 | public synchronized String toString(Calendar cal, Time x)
48 | {
49 | if (cal == null)
50 | cal = defaultCal;
51 |
52 | cal.setTime(x);
53 | sbuf.setLength(0);
54 |
55 | appendTime(sbuf, cal, cal.get(Calendar.MILLISECOND) * 1000000);
56 |
57 | appendTimeZone(sbuf, cal);
58 |
59 | return sbuf.toString();
60 | }
61 |
62 | public synchronized String toString(Calendar cal, Date x)
63 | {
64 | if (cal == null)
65 | cal = defaultCal;
66 |
67 | cal.setTime(x);
68 | sbuf.setLength(0);
69 |
70 | appendDate(sbuf, cal);
71 | appendEra(sbuf, cal);
72 | appendTimeZone(sbuf, cal);
73 |
74 | return sbuf.toString();
75 | }
76 | private static final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd") ;
77 |
78 | public synchronized Date parse(String string) throws Exception
79 | {
80 | return new Date(df.parse(string).getTime());
81 | }
82 |
83 | private static final SimpleDateFormat tf = new SimpleDateFormat("HH:mm:ss");
84 |
85 | public synchronized Time parseTime(String string) throws Exception
86 | {
87 | return new Time(tf.parse(string).getTime());
88 | }
89 |
90 | private static final SimpleDateFormat tsf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
91 |
92 | public synchronized Timestamp parseTimestamp(String json) throws Exception
93 | {
94 | // parse the date part
95 | Timestamp ts = new Timestamp( tsf.parse(json).getTime() );
96 |
97 |
98 | // now find the nanos
99 | String [] parts = json.split("(\\.)|( )");
100 |
101 | // parts may or may not have timezone information if timezone then it will be 4, otherwise 3
102 | if (parts.length >2 )
103 | {
104 | int nanos = Integer.parseInt(parts[2]);
105 |
106 |
107 | for (int numlength = parts[2].length(); numlength < 9; ++numlength)
108 | nanos *= 10;
109 | ts.setNanos(nanos);
110 | }
111 |
112 | return ts;
113 | }
114 |
115 | public Time applyCalendar( Calendar cal, Time time)
116 | {
117 | // check to see if there is a calendar and that it is different than the one used to parse
118 | if ( !cal.getTimeZone().hasSameRules(tf.getTimeZone()))
119 | {
120 | Calendar convertCal = Calendar.getInstance();
121 | convertCal.setTime(time);
122 | TimeZone toTimeZone = cal.getTimeZone();
123 | TimeZone fromTimeZone = tf.getTimeZone();
124 |
125 | convertCal.setTimeZone(fromTimeZone);
126 | convertCal.add(Calendar.MILLISECOND, fromTimeZone.getRawOffset() * -1);
127 | if (fromTimeZone.inDaylightTime(convertCal.getTime())) {
128 | convertCal.add(Calendar.MILLISECOND, convertCal.getTimeZone().getDSTSavings() * -1);
129 | }
130 |
131 | convertCal.add(Calendar.MILLISECOND, toTimeZone.getRawOffset());
132 | if (toTimeZone.inDaylightTime(convertCal.getTime())) {
133 | convertCal.add(Calendar.MILLISECOND, toTimeZone.getDSTSavings());
134 | }
135 |
136 | return new Time(convertCal.getTime().getTime());
137 | }
138 | return time;
139 | }
140 |
141 | public Date applyCalendar(Calendar cal, Date date)
142 | {
143 | // check to see if there is a calendar and that it is different than the one used to parse
144 | if ( !cal.getTimeZone().hasSameRules(df.getTimeZone()))
145 | {
146 | Calendar convertCal = Calendar.getInstance();
147 | convertCal.setTime(date);
148 | TimeZone toTimeZone = cal.getTimeZone();
149 | TimeZone fromTimeZone = df.getTimeZone();
150 |
151 | convertCal.setTimeZone(fromTimeZone);
152 | convertCal.add(Calendar.MILLISECOND, fromTimeZone.getRawOffset() * -1);
153 | if (fromTimeZone.inDaylightTime(convertCal.getTime())) {
154 | convertCal.add(Calendar.MILLISECOND, convertCal.getTimeZone().getDSTSavings() * -1);
155 | }
156 |
157 | convertCal.add(Calendar.MILLISECOND, toTimeZone.getRawOffset());
158 | if (toTimeZone.inDaylightTime(convertCal.getTime())) {
159 | convertCal.add(Calendar.MILLISECOND, toTimeZone.getDSTSavings());
160 | }
161 |
162 | return new Date(convertCal.getTime().getTime());
163 | }
164 | return date;
165 | }
166 |
167 | public Timestamp applyCalendar(Calendar cal, Timestamp timestamp)
168 | {
169 | if ( !cal.getTimeZone().hasSameRules(tsf.getTimeZone()))
170 | {
171 | Calendar convertCal = Calendar.getInstance();
172 | convertCal.setTime(timestamp);
173 | TimeZone toTimeZone = cal.getTimeZone();
174 | TimeZone fromTimeZone = tsf.getTimeZone();
175 |
176 | convertCal.setTimeZone(fromTimeZone);
177 | convertCal.add(Calendar.MILLISECOND, fromTimeZone.getRawOffset() * -1);
178 | if (fromTimeZone.inDaylightTime(convertCal.getTime())) {
179 | convertCal.add(Calendar.MILLISECOND, convertCal.getTimeZone().getDSTSavings() * -1);
180 | }
181 |
182 | convertCal.add(Calendar.MILLISECOND, toTimeZone.getRawOffset());
183 | if (toTimeZone.inDaylightTime(convertCal.getTime())) {
184 | convertCal.add(Calendar.MILLISECOND, toTimeZone.getDSTSavings());
185 | }
186 |
187 | return new Timestamp(convertCal.getTime().getTime());
188 | }
189 | return timestamp;
190 | }
191 | private static void appendDate(StringBuffer sb, Calendar cal)
192 | {
193 | int l_year = cal.get(Calendar.YEAR);
194 | // always use at least four digits for the year so very
195 | // early years, like 2, don't get misinterpreted
196 | //
197 | int l_yearlen = String.valueOf(l_year).length();
198 | for (int i = 4; i > l_yearlen; i--)
199 | {
200 | sb.append("0");
201 | }
202 |
203 | sb.append(l_year);
204 | sb.append('-');
205 | int l_month = cal.get(Calendar.MONTH) + 1;
206 | if (l_month < 10)
207 | sb.append('0');
208 | sb.append(l_month);
209 | sb.append('-');
210 | int l_day = cal.get(Calendar.DAY_OF_MONTH);
211 | if (l_day < 10)
212 | sb.append('0');
213 | sb.append(l_day);
214 | }
215 |
216 | private static void appendTime(StringBuffer sb, Calendar cal, int nanos)
217 | {
218 | int hours = cal.get(Calendar.HOUR_OF_DAY);
219 | if (hours < 10)
220 | sb.append('0');
221 | sb.append(hours);
222 |
223 | sb.append(':');
224 | int minutes = cal.get(Calendar.MINUTE);
225 | if (minutes < 10)
226 | sb.append('0');
227 | sb.append(minutes);
228 |
229 | sb.append(':');
230 | int seconds = cal.get(Calendar.SECOND);
231 | if (seconds < 10)
232 | sb.append('0');
233 | sb.append(seconds);
234 |
235 | // Add nanoseconds.
236 | // This won't work for server versions < 7.2 which only want
237 | // a two digit fractional second, but we don't need to support 7.1
238 | // anymore and getting the version number here is difficult.
239 | //
240 | char[] decimalStr = {'0', '0', '0', '0', '0', '0', '0', '0', '0'};
241 | char[] nanoStr = Integer.toString(nanos).toCharArray();
242 | System.arraycopy(nanoStr, 0, decimalStr, decimalStr.length - nanoStr.length, nanoStr.length);
243 | sb.append('.');
244 | sb.append(decimalStr, 0, 6);
245 | }
246 |
247 | private void appendTimeZone(StringBuffer sb, java.util.Calendar cal)
248 | {
249 | int offset = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)) / 1000;
250 |
251 | int absoff = Math.abs(offset);
252 | int hours = absoff / 60 / 60;
253 | int mins = (absoff - hours * 60 * 60) / 60;
254 | int secs = absoff - hours * 60 * 60 - mins * 60;
255 |
256 | sb.append((offset >= 0) ? " +" : " -");
257 |
258 | if (hours < 10)
259 | sb.append('0');
260 | sb.append(hours);
261 |
262 | sb.append(':');
263 |
264 | if (mins < 10)
265 | sb.append('0');
266 | sb.append(mins);
267 |
268 | sb.append(':');
269 | if (secs < 10)
270 | sb.append('0');
271 | sb.append(secs);
272 | }
273 |
274 | private static void appendEra(StringBuffer sb, Calendar cal)
275 | {
276 | if (cal.get(Calendar.ERA) == GregorianCalendar.BC) {
277 | sb.append(" BC");
278 | }
279 | }
280 |
281 | }
282 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
--------------------------------------------------------------------------------
/src/main/java/com/couchbase/jdbc/CBDriver.java:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | * // Copyright (c) 2015 Couchbase, Inc.
4 | * // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 | * // except in compliance with the License. You may obtain a copy of the License at
6 | * // http://www.apache.org/licenses/LICENSE-2.0
7 | * // Unless required by applicable law or agreed to in writing, software distributed under the
8 | * // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
9 | * // either express or implied. See the License for the specific language governing permissions
10 | * // and limitations under the License.
11 | */
12 |
13 | package com.couchbase.jdbc;
14 |
15 |
16 | import ch.qos.logback.classic.Level;
17 | import ch.qos.logback.classic.Logger;
18 |
19 | import org.slf4j.LoggerFactory;
20 |
21 | import java.sql.*;
22 | import java.text.MessageFormat;
23 | import java.util.Properties;
24 | import java.util.concurrent.ConcurrentLinkedQueue;
25 |
26 |
27 | public class CBDriver implements java.sql.Driver
28 | {
29 | public static final org.slf4j.Logger logger = LoggerFactory.getLogger(CBDriver.class.getName());
30 |
31 | public static final int MAJOR_VERSION = 1;
32 |
33 | public static final int MINOR_VERSION = 1;
34 |
35 | public static final String DRIVER_NAME = "n1ql_jdbc";
36 |
37 | static CBDriver registered;
38 |
39 | final Thread houseKeepingThread;
40 | final ClusterThread ct;
41 | static
42 | {
43 | try
44 | {
45 | registered = new CBDriver();
46 | java.sql.DriverManager.registerDriver(registered);
47 |
48 | }
49 | catch (SQLException e)
50 | {
51 | logger.error("Error registering driver", e);
52 | }
53 |
54 |
55 | }
56 |
57 | public CBDriver() throws SQLException
58 | {
59 | ct = new ClusterThread();
60 | houseKeepingThread = new Thread(ct, "Couchbase housekeeping thread");
61 | houseKeepingThread.setDaemon(true);
62 | houseKeepingThread.start();
63 | }
64 | /**
65 | * Attempts to make a database connection to the given URL.
66 | * The driver should return "null" if it realizes it is the wrong kind
67 | * of driver to connect to the given URL. This will be common, as when
68 | * the JDBC driver manager is asked to connect to a given URL it passes
69 | * the URL to each loaded driver in turn.
70 | *
71 | * The driver should throw an SQLException if it is the right
72 | * driver to connect to the given URL but has trouble connecting to
73 | * the database.
74 | *
75 | *
The java.util.Properties argument can be used to pass
76 | * arbitrary string tag/value pairs as connection arguments.
77 | * Normally at least "user" and "password" properties should be
78 | * included in the Properties object.
79 | *
80 | * @param url the URL of the database to which to connect
81 | * @param info a list of arbitrary string tag/value pairs as
82 | * connection arguments. Normally at least a "user" and
83 | * "password" property should be included.
84 | * @return a Connection object that represents a
85 | * connection to the URL
86 | * @throws java.sql.SQLException if a database access error occurs
87 | */
88 | @Override
89 | public Connection connect(String url, Properties info) throws SQLException
90 | {
91 |
92 | if (acceptsURL(url))
93 | {
94 | CBConnection con = new CBConnection(url, info);
95 | ct.addConnection(con);
96 | return con;
97 | }
98 | else
99 | {
100 | return null;
101 | }
102 | }
103 |
104 | /**
105 | * Retrieves whether the driver thinks that it can open a connection
106 | * to the given URL. Typically drivers will return true if they
107 | * understand the subprotocol specified in the URL and false if
108 | * they do not.
109 | *
110 | * @param url the URL of the database
111 | * @return true if this driver understands the given URL;
112 | * false otherwise
113 | * @throws java.sql.SQLException if a database access error occurs
114 | */
115 | @Override
116 | public boolean acceptsURL(String url) throws SQLException
117 | {
118 | return url.startsWith("jdbc:couchbase:");
119 | }
120 |
121 | /**
122 | * Gets information about the possible properties for this driver.
123 | *
124 | * The getPropertyInfo method is intended to allow a generic
125 | * GUI tool to discover what properties it should prompt
126 | * a human for in order to get
127 | * enough information to connect to a database. Note that depending on
128 | * the values the human has supplied so far, additional values may become
129 | * necessary, so it may be necessary to iterate though several calls
130 | * to the getPropertyInfo method.
131 | *
132 | * @param url the URL of the database to which to connect
133 | * @param info a proposed list of tag/value pairs that will be sent on
134 | * connect open
135 | * @return an array of DriverPropertyInfo objects describing
136 | * possible properties. This array may be an empty array if
137 | * no properties are required.
138 | * @throws java.sql.SQLException if a database access error occurs
139 | */
140 | @Override
141 | public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException
142 | {
143 | return new DriverPropertyInfo[0];
144 | }
145 |
146 | /**
147 | * Retrieves the driver's major version number. Initially this should be 1.
148 | *
149 | * @return this driver's major version number
150 | */
151 | @Override
152 | public int getMajorVersion()
153 | {
154 | return 0;
155 | }
156 |
157 | /**
158 | * Gets the driver's minor version number. Initially this should be 0.
159 | *
160 | * @return this driver's minor version number
161 | */
162 | @Override
163 | public int getMinorVersion()
164 | {
165 | return 0;
166 | }
167 |
168 | /**
169 | * Reports whether this driver is a genuine JDBC
170 | * Compliant(TM) driver.
171 | * A driver may only report true here if it passes the JDBC
172 | * compliance tests; otherwise it is required to return false.
173 | *
174 | * JDBC compliance requires full support for the JDBC API and full support
175 | * for SQL 92 Entry Level. It is expected that JDBC compliant drivers will
176 | * be available for all the major commercial databases.
177 | *
178 | * This method is not intended to encourage the development of non-JDBC
179 | * compliant drivers, but is a recognition of the fact that some vendors
180 | * are interested in using the JDBC API and framework for lightweight
181 | * databases that do not support full database functionality, or for
182 | * special databases such as document information retrieval where a SQL
183 | * implementation may not be feasible.
184 | *
185 | * @return true if this driver is JDBC Compliant; false
186 | * otherwise
187 | */
188 | @Override
189 | public boolean jdbcCompliant()
190 | {
191 | return false;
192 | }
193 |
194 | private static final MessageFormat mf = new MessageFormat("Method {0}.{1} is not yet implemented.");
195 |
196 | public static java.sql.SQLFeatureNotSupportedException notImplemented(Class callClass, String functionName)
197 | {
198 |
199 | return new java.sql.SQLFeatureNotSupportedException(mf.format(new Object [] {callClass.getName(),functionName}));
200 | }
201 |
202 | public static void setLogLevel(Level logLevel)
203 | {
204 | synchronized (CBDriver.class)
205 | {
206 | Logger logger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("com.couchbase");
207 | logger.setLevel(logLevel);
208 | //logLevelSet = true;
209 | }
210 | }
211 |
212 | public static Level getLogLevel()
213 | {
214 | synchronized (CBDriver.class)
215 | {
216 | Logger logger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("com.couchbase");
217 | return logger.getLevel();
218 | }
219 | }
220 |
221 | /**
222 | * Return the parent Logger of all the Loggers used by this driver. This
223 | * should be the Logger farthest from the root Logger that is
224 | * still an ancestor of all of the Loggers used by this driver. Configuring
225 | * this Logger will affect all of the log messages generated by the driver.
226 | * In the worst case, this may be the root Logger.
227 | *
228 | * @return the parent Logger for this driver
229 | * @throws java.sql.SQLFeatureNotSupportedException if the driver does not use java.util.logging.
230 | * @since 1.7
231 | */
232 | @Override
233 | public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException
234 | {
235 | throw notImplemented(CBDriver.class, "getParentLogger");
236 | }
237 |
238 | public static void cleanup()
239 | {
240 | if (registered != null)
241 | {
242 | try
243 | {
244 | DriverManager.deregisterDriver(registered);
245 |
246 | //stop the thread below
247 | runCluster=false;
248 | Thread.currentThread().interrupt();
249 |
250 |
251 | }
252 | catch (SQLException e)
253 | {
254 | logger.warn("Error deregistering driver", e);
255 | }
256 | }
257 | }
258 | public void cleanup(CBConnection con)
259 | {
260 | ct.removeConnection(con);
261 | }
262 |
263 | static boolean runCluster=true;
264 |
265 | private static class ClusterThread implements Runnable
266 | {
267 |
268 | ConcurrentLinkedQueue connections;
269 | ClusterThread()
270 | {
271 | connections = new ConcurrentLinkedQueue();
272 | }
273 | @Override
274 | public void run()
275 | {
276 | while(runCluster)
277 | {
278 | CBConnection connection = connections.poll();
279 | if ( connection != null )
280 | {
281 | try
282 | {
283 | connection.pollCluster();
284 | } catch (SQLException e)
285 | {
286 | logger.error("Error polling cluster", e);
287 | }
288 | }
289 | try
290 | {
291 | Thread.sleep(1000);
292 | }
293 | catch (InterruptedException e)
294 | {
295 | // ignore it
296 | }
297 | }
298 | }
299 | public void addConnection(CBConnection connection)
300 | {
301 | connections.add(connection);
302 | }
303 | public void removeConnection(CBConnection connection)
304 | {
305 | connections.remove(connection);
306 | }
307 | }
308 | }
309 |
--------------------------------------------------------------------------------
/src/main/java/com/couchbase/jdbc/core/Parser.java:
--------------------------------------------------------------------------------
1 | /*
2 | * // Copyright (c) 2015 Couchbase, Inc.
3 | * // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
4 | * // except in compliance with the License. You may obtain a copy of the License at
5 | * // http://www.apache.org/licenses/LICENSE-2.0
6 | * // Unless required by applicable law or agreed to in writing, software distributed under the
7 | * // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
8 | * // either express or implied. See the License for the specific language governing permissions
9 | * // and limitations under the License.
10 | */
11 |
12 | package com.couchbase.jdbc.core;
13 | /*-------------------------------------------------------------------------
14 | *
15 | * Copyright (c) 2006, PostgreSQL Global Development Group
16 | *
17 | *
18 | *-------------------------------------------------------------------------
19 | */
20 |
21 |
22 | /**
23 | * Basic query parser infrastructure.
24 | *
25 | * @author Michael Paesold (mpaesold@gmx.at)
26 | */
27 | public class Parser {
28 |
29 | /*
30 | * Find the end of the single-quoted string starting at the given offset.
31 | *
32 | * Note: for 'single '' quote in string', this method currently
33 | * returns the offset of first ' character after the initial
34 | * one. The caller must call the method a second time for the second
35 | * part of the quoted string.
36 | * @param query
37 | * @param offset
38 | * @return
39 | */
40 | public static int parseSingleQuotes(final char[] query, int offset)
41 | {
42 |
43 | // treat backslashes as escape characters
44 | while (++offset < query.length)
45 | {
46 | switch (query[offset])
47 | {
48 | case '\\':
49 | ++offset;
50 | break;
51 | case '\'':
52 | return offset;
53 | default:
54 | break;
55 | }
56 | }
57 |
58 | return query.length;
59 | }
60 |
61 | /*
62 | * Find the end of the double-quoted string starting at the given offset.
63 | *
64 | * Note: for "double "" quote in string",
65 | * this method currently returns the offset of first "
66 | * character after the initial one. The caller must call the method a
67 | * second time for the second part of the quoted string.
68 | * @param query
69 | * @param offset
70 | * @return
71 | */
72 | public static int parseDoubleQuotes(final char[] query, int offset)
73 | {
74 | while (++offset < query.length && query[offset] != '"') ;
75 | return offset;
76 | }
77 |
78 | /*
79 | * Test if the dollar character ($) at the given offset starts
80 | * a dollar-quoted string and return the offset of the ending dollar
81 | * character.
82 | * @param query
83 | * @param offset
84 | * @return
85 | */
86 | public static int parseDollarQuotes(final char[] query, int offset) {
87 | if (offset + 1 < query.length
88 | && (offset == 0 || !isIdentifierContChar(query[offset-1])))
89 | {
90 | int endIdx = -1;
91 | if (query[offset + 1] == '$')
92 | endIdx = offset + 1;
93 | else if (isDollarQuoteStartChar(query[offset + 1]))
94 | {
95 | for (int d = offset + 2; d < query.length; ++d)
96 | {
97 | if (query[d] == '$')
98 | {
99 | endIdx = d;
100 | break;
101 | }
102 | else if (!isDollarQuoteContChar(query[d]))
103 | break;
104 | }
105 | }
106 | if (endIdx > 0)
107 | {
108 | // found; note: tag includes start and end $ character
109 | int tagIdx = offset, tagLen = endIdx - offset + 1;
110 | offset = endIdx; // loop continues at endIdx + 1
111 | for (++offset; offset < query.length; ++offset)
112 | {
113 | if (query[offset] == '$' &&
114 | subArraysEqual(query, tagIdx, offset, tagLen))
115 | {
116 | offset += tagLen - 1;
117 | break;
118 | }
119 | }
120 | }
121 | }
122 | return offset;
123 | }
124 |
125 | /*
126 | * Test if the - character at offset starts a
127 | * -- style line comment, and return the position of the first
128 | * \r or \n character.
129 | * @param query
130 | * @param offset
131 | * @return
132 | */
133 | public static int parseLineComment(final char[] query, int offset) {
134 | if (offset + 1 < query.length && query[offset + 1] == '-')
135 | {
136 | while (offset + 1 < query.length)
137 | {
138 | offset++;
139 | if (query[offset] == '\r' || query[offset] == '\n')
140 | break;
141 | }
142 | }
143 | return offset;
144 | }
145 |
146 | /*
147 | * Test if the / character at offset starts a block
148 | * comment, and return the position of the last / character.
149 | * @param query
150 | * @param offset
151 | * @return
152 | */
153 | public static int parseBlockComment(final char[] query, int offset) {
154 | if (offset + 1 < query.length && query[offset + 1] == '*')
155 | {
156 | // /* /* */ */ nest, according to SQL spec
157 | int level = 1;
158 | for (offset += 2; offset < query.length; ++offset)
159 | {
160 | switch (query[offset-1])
161 | {
162 | case '*':
163 | if (query[offset] == '/')
164 | {
165 | --level;
166 | ++offset; // don't parse / in */* twice
167 | }
168 | break;
169 | case '/':
170 | if (query[offset] == '*')
171 | {
172 | ++level;
173 | ++offset; // don't parse * in /*/ twice
174 | }
175 | break;
176 | default:
177 | break;
178 | }
179 |
180 | if (level == 0)
181 | {
182 | --offset; // reset position to last '/' char
183 | break;
184 | }
185 | }
186 | }
187 | return offset;
188 | }
189 |
190 | /**
191 | * unmark '??' in query back to '?'
192 | * @param query query to remove question marks from
193 | * @return modified query
194 | */
195 | public static String unmarkDoubleQuestion( String query )
196 | {
197 | if (query == null) return query;
198 |
199 | char[] aChars = query.toCharArray();
200 | StringBuilder buf = new StringBuilder(aChars.length);
201 | for(int i=0, j; i< aChars.length; i++)
202 | {
203 | switch (aChars[i])
204 | {
205 | case '\'': // single-quotes
206 | j = Parser.parseSingleQuotes(aChars, i);
207 | buf.append(aChars, i, j-i+1);
208 | i = j;
209 | break;
210 |
211 | case '"': // double-quotes
212 | j = Parser.parseDoubleQuotes(aChars, i);
213 | buf.append(aChars, i, j-i+1);
214 | i = j;
215 | break;
216 |
217 | case '-': // possibly -- style comment
218 | j = Parser.parseLineComment(aChars, i);
219 | buf.append(aChars, i, j-i+1);
220 | i = j;
221 | break;
222 |
223 | case '/': // possibly /* */ style comment
224 | j = Parser.parseBlockComment(aChars, i);
225 | buf.append(aChars, i, j-i+1);
226 | i = j;
227 | break;
228 |
229 | case '$': // possibly dollar quote start
230 | j = Parser.parseDollarQuotes(aChars, i);
231 | buf.append(aChars, i, j-i+1);
232 | i = j;
233 | break;
234 |
235 | case '?': // unescape '??' back to '?'
236 | if (i+1 < aChars.length && aChars[i+1] == '?') {
237 | buf.append("?");
238 | i = i+1;
239 | } else buf.append("?");
240 |
241 | break;
242 |
243 | default:
244 | buf.append(aChars[i]);
245 | }
246 | }
247 |
248 | return buf.toString();
249 | }
250 |
251 | /*
252 | * @param c
253 | * @return true if the character is a whitespace character as defined
254 | * in the backend's parser
255 | */
256 | public static boolean isSpace(char c) {
257 | return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f';
258 | }
259 |
260 | /*
261 | * @param c
262 | * @return true if the given character is a valid character for an
263 | * operator in the backend's parser
264 | */
265 | public static boolean isOperatorChar(char c) {
266 | /*
267 | * Extracted from operators defined by {self} and {op_chars}
268 | * in pgsql/src/backend/parser/scan.l.
269 | */
270 | return ",()[].;:+-*/%^<>=~!@#&|`?".indexOf(c) != -1;
271 | }
272 |
273 | /**
274 | * Checks if a character is valid as the start of an identifier.
275 | *
276 | * @param c the character to check
277 | * @return true if valid as first character of an identifier; false if not
278 | */
279 | public static boolean isIdentifierStartChar(char c) {
280 | /*
281 | * Extracted from {ident_start} and {ident_cont} in
282 | * pgsql/src/backend/parser/scan.l:
283 | * ident_start [A-Za-z\200-\377_]
284 | * ident_cont [A-Za-z\200-\377_0-9\$]
285 | */
286 | return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
287 | || c == '_' || c > 127 ;
288 | }
289 |
290 | /**
291 | * Checks if a character is valid as the second or later character of an
292 | * identifier.
293 | *
294 | * @param c the character to check
295 | * @return true if valid as second or later character of an identifier; false if not
296 | */
297 | public static boolean isIdentifierContChar(char c) {
298 | return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
299 | || c == '_' || c > 127
300 | || (c >= '0' && c <= '9')
301 | || c == '$';
302 | }
303 |
304 | /*
305 | * @param c
306 | * @return true if the character terminates an identifier
307 | */
308 | public static boolean charTerminatesIdentifier(char c) {
309 | return c == '"' || isSpace(c) || isOperatorChar(c);
310 | }
311 |
312 | /**
313 | * Checks if a character is valid as the start of a dollar quoting tag.
314 | *
315 | * @param c the character to check
316 | * @return true if valid as first character of a dollar quoting tag; false if not
317 | */
318 | public static boolean isDollarQuoteStartChar(char c) {
319 | /*
320 | * The allowed dollar quote start and continuation characters
321 | * must stay in sync with what the backend defines in
322 | * pgsql/src/backend/parser/scan.l
323 | */
324 | return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
325 | || c == '_' || c > 127;
326 | }
327 |
328 | /**
329 | * Checks if a character is valid as the second or later character of a
330 | * dollar quoting tag.
331 | *
332 | * @param c the character to check
333 | * @return true if valid as second or later character of a dollar quoting tag;
334 | * false if not
335 | */
336 | public static boolean isDollarQuoteContChar(char c) {
337 | return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
338 | || c == '_' || c > 127
339 | || (c >= '0' && c <= '9');
340 | }
341 |
342 | /**
343 | * Compares two sub-arrays of the given character array for equalness.
344 | * If the length is zero, the result is true if and only if the offsets
345 | * are within the bounds of the array.
346 | *
347 | * @param arr a char array
348 | * @param offA first sub-array start offset
349 | * @param offB second sub-array start offset
350 | * @param len length of the sub arrays to compare
351 | * @return true if the sub-arrays are equal; false if not
352 | */
353 | private static boolean subArraysEqual(final char[] arr,
354 | final int offA, final int offB,
355 | final int len) {
356 | if (offA < 0 || offB < 0
357 | || offA >= arr.length || offB >= arr.length
358 | || offA + len > arr.length || offB + len > arr.length)
359 | return false;
360 |
361 | for (int i = 0; i < len; ++i)
362 | {
363 | if (arr[offA + i] != arr[offB + i])
364 | return false;
365 | }
366 |
367 | return true;
368 | }
369 | }
370 |
371 |
--------------------------------------------------------------------------------
/src/test/java/com/couchbase/jdbc/SqlParserTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * // Copyright (c) 2015 Couchbase, Inc.
3 | * // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
4 | * // except in compliance with the License. You may obtain a copy of the License at
5 | * // http://www.apache.org/licenses/LICENSE-2.0
6 | * // Unless required by applicable law or agreed to in writing, software distributed under the
7 | * // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
8 | * // either express or implied. See the License for the specific language governing permissions
9 | * // and limitations under the License.
10 | */
11 |
12 | package com.couchbase.jdbc;
13 |
14 | import com.couchbase.jdbc.CouchBaseTestCase;
15 | import com.couchbase.jdbc.util.SqlParser;
16 | import org.junit.Test;
17 | import org.junit.runner.RunWith;
18 | import org.junit.runners.JUnit4;
19 |
20 | import java.sql.*;
21 | import java.util.Calendar;
22 | import java.util.Date;
23 |
24 | import static org.junit.Assert.*;
25 | import static org.junit.Assert.assertNotNull;
26 |
27 | /**
28 | * Created by davec on 2015-09-18.
29 | */
30 | @RunWith(JUnit4.class)
31 | public class SqlParserTest extends CouchBaseTestCase
32 | {
33 |
34 | @Test
35 | public void testReplaceProcessing() throws Exception
36 | {
37 | SqlParser sqlParser = new SqlParser("select {fn acos(-0.6)} as acos ");
38 | String replaced = sqlParser.replaceProcessing("select {fn acos(-0.6)} as acos",true);
39 | assertEquals("select acos(-0.6) as acos", replaced);
40 |
41 | replaced = sqlParser.replaceProcessing("insert into escapetest (ts) values ({ts '1900-01-01 00:00:00'})", true);
42 | assertEquals("insert into escapetest (ts) values ( '1900-01-01 00:00:00')", replaced);
43 |
44 | replaced = sqlParser.replaceProcessing("insert into escapetest (d) values ({d '1900-01-01'})",true);
45 | assertEquals("insert into escapetest (d) values (DATE '1900-01-01')", replaced);
46 |
47 | replaced = sqlParser.replaceProcessing("insert into escapetest (t) values ({t '00:00:00'})",true);
48 | assertEquals("insert into escapetest (t) values ( '00:00:00')", replaced);
49 |
50 | replaced = sqlParser.replaceProcessing("select {fn version()} as version", true);
51 | assertEquals("select version() as version",replaced);
52 |
53 | replaced = sqlParser.replaceProcessing("select {fn version()} as version, {fn log({fn log(3.0)})} as log", true);
54 | assertEquals("select version() as version, ln(ln(3.0)) as log",replaced);
55 |
56 | replaced = sqlParser.replaceProcessing("select * from {oj test_statement a left outer join b on (a.i=b.i)} ", true);
57 | assertEquals("select * from test_statement a left outer join b on (a.i=b.i) ",replaced);
58 |
59 | }
60 |
61 | @Test
62 | public void testNumericFunctions() throws Exception
63 | {
64 | Statement stmt = con.createStatement();
65 |
66 | ResultSet rs = stmt.executeQuery("select {fn abs(-2.3)} as abs ");
67 | assertTrue(rs.next());
68 | assertEquals(2.3f, rs.getFloat(1), 0.00001);
69 |
70 | rs = stmt.executeQuery("select {fn acos(-0.6)} as acos ");
71 | assertTrue(rs.next());
72 | assertEquals(Math.acos(-0.6), rs.getDouble(1), 0.00001);
73 |
74 | rs = stmt.executeQuery("select {fn asin(-0.6)} as asin ");
75 | assertTrue(rs.next());
76 | assertEquals(Math.asin(-0.6), rs.getDouble(1), 0.00001);
77 |
78 | rs = stmt.executeQuery("select {fn atan(-0.6)} as atan ");
79 | assertTrue(rs.next());
80 | assertEquals(Math.atan(-0.6), rs.getDouble(1), 0.00001);
81 |
82 | rs = stmt.executeQuery("select {fn atan2(-2.3,7)} as atan2 ");
83 | assertTrue(rs.next());
84 | assertEquals(Math.atan2(-2.3,7), rs.getDouble(1), 0.00001);
85 |
86 | rs = stmt.executeQuery("select {fn ceiling(-2.3)} as ceiling ");
87 | assertTrue(rs.next());
88 | assertEquals(-2, rs.getDouble(1), 0.00001);
89 |
90 | rs = stmt.executeQuery("select {fn cos(-2.3)} as cos, {fn cot(-2.3)} as cot ");
91 | assertTrue(rs.next());
92 | assertEquals(Math.cos(-2.3), rs.getDouble(1), 0.00001);
93 | assertEquals(1/Math.tan(-2.3), rs.getDouble(2), 0.00001);
94 |
95 | rs = stmt.executeQuery("select {fn degrees({fn pi()})} as degrees ");
96 | assertTrue(rs.next());
97 | assertEquals(180, rs.getDouble(1), 0.00001);
98 |
99 | rs = stmt.executeQuery("select {fn exp(-2.3)}, {fn floor(-2.3)}," +
100 | " {fn log(2.3)},{fn log10(2.3)},{fn mod(3,2)}");
101 | assertTrue(rs.next());
102 | assertEquals(Math.exp(-2.3), rs.getDouble(1), 0.00001);
103 | assertEquals(-3, rs.getDouble(2), 0.00001);
104 | assertEquals(Math.log(2.3), rs.getDouble(3), 0.00001);
105 | assertEquals(Math.log(2.3)/Math.log(10), rs.getDouble(4), 0.00001);
106 | assertEquals(1, rs.getDouble(5), 0.00001);
107 |
108 | rs = stmt.executeQuery("select {fn pi()}, {fn power(7,-2.3)}," +
109 | " {fn radians(-180)},{fn round(3.1294,2)}");
110 | assertTrue(rs.next());
111 | assertEquals(Math.PI, rs.getDouble(1), 0.00001);
112 | assertEquals(Math.pow(7,-2.3), rs.getDouble(2), 0.00001);
113 | assertEquals(-Math.PI, rs.getDouble(3), 0.00001);
114 | assertEquals(3.13, rs.getDouble(4), 0.00001);
115 |
116 | rs = stmt.executeQuery("select {fn sign(-2.3)}, {fn sin(-2.3)}," +
117 | " {fn sqrt(2.3)},{fn tan(-2.3)},{fn truncate(3.1294,2)}");
118 | assertTrue(rs.next());
119 | assertEquals(-1, rs.getInt(1));
120 | assertEquals(Math.sin(-2.3), rs.getDouble(2), 0.00001);
121 | assertEquals(Math.sqrt(2.3), rs.getDouble(3), 0.00001);
122 | assertEquals(Math.tan(-2.3), rs.getDouble(4), 0.00001);
123 | assertEquals(3.12, rs.getDouble(5), 0.00001);
124 | }
125 |
126 | @Test
127 | public void testStringFunctions() throws SQLException
128 | {
129 | Statement stmt = con.createStatement();
130 | ResultSet rs = stmt.executeQuery("select {fn concat('ab','cd')}" +
131 | ",{fn lcase('aBcD')},{fn left('1234',2)},{fn length('123 ')}" +
132 | ",{fn locate('bc','abc')},{fn locate('bc','abc',3)}");
133 | assertTrue(rs.next());
134 | assertEquals("abcd",rs.getString(1));
135 | assertEquals("abcd",rs.getString(2));
136 | assertEquals("12",rs.getString(3));
137 | assertEquals(3,rs.getInt(4));
138 | assertEquals(2,rs.getInt(5));
139 | assertEquals(0,rs.getInt(6));
140 |
141 | rs = stmt.executeQuery("SELECT {fn insert('abcdef',3,2,'xxxx')}" +
142 | ",{fn replace('abcdbc','bc','x')}");
143 | assertTrue(rs.next());
144 | assertEquals("abxxxxef",rs.getString(1));
145 | assertEquals("axdx",rs.getString(2));
146 |
147 | rs = stmt.executeQuery("select {fn ltrim(' ab')},{fn repeat('ab',2)}" +
148 | ",{fn right('abcde',2)},{fn rtrim('ab ')}" +
149 | ",{fn space(3)},{fn substring('abcd',2,2)}" +
150 | ",{fn ucase('aBcD')}");
151 | assertTrue(rs.next());
152 | assertEquals("ab",rs.getString(1));
153 | assertEquals("abab",rs.getString(2));
154 | assertEquals("de",rs.getString(3));
155 | assertEquals("ab",rs.getString(4));
156 | assertEquals(" ",rs.getString(5));
157 | assertEquals("bc",rs.getString(6));
158 | assertEquals("ABCD",rs.getString(7));
159 | }
160 |
161 | @Test
162 | public void testDateFunctions() throws SQLException
163 | {
164 | Calendar expected = Calendar.getInstance(), actual=Calendar.getInstance();
165 |
166 | Statement stmt = con.createStatement();
167 | ResultSet rs = stmt.executeQuery("select {fn curdate()} as curdate,{fn curtime()} as curtime" +
168 | ",{fn dayname('2015-02-22 12:00:00')} as dayname , {fn dayofmonth('2015-02-22 12:00:00')} as dayofmonth" +
169 | ",{fn dayofweek({ts '2015-02-22 12:00:00'})} as dayofweek,{fn dayofyear('2015-02-22 12:00:00')} as dayofyear" +
170 | ",{fn hour('2015-02-22 12:00:00')} as hour,{fn minute('2015-02-22 12:00:00')} as minute" +
171 | ",{fn month('2015-02-22 12:00:00')} as month" +
172 | ",{fn monthname('2015-02-22 12:00:00')} as monthname,{fn quarter('2015-02-22 12:00:00')} as quarter" +
173 | ",{fn second('2015-02-22 12:00:00')} as second,{fn week('2015-02-22 12:00:00')} as week" +
174 | ",{fn year('2015-02-22 12:00:00')} as year");
175 | assertTrue(rs.next());
176 |
177 | Date date = rs.getDate("curdate");
178 | actual.setTime(date);
179 |
180 | assertEquals(expected.get(Calendar.DAY_OF_MONTH),actual.get(Calendar.DAY_OF_MONTH));
181 | assertEquals(expected.get(Calendar.YEAR), actual.get(Calendar.YEAR));
182 | assertEquals(expected.get(Calendar.MONTH),actual.get(Calendar.MONTH));
183 |
184 | Time time = rs.getTime("curtime");
185 | assertNotNull(time);
186 |
187 | String dayName = rs.getString("dayname");
188 | assertEquals("SUN", dayName);
189 |
190 | int dayOfMonth = rs.getInt("dayofmonth");
191 | assertEquals(22, dayOfMonth);
192 |
193 | int dayOfWeek = rs.getInt("dayofweek");
194 | assertEquals(7,dayOfWeek);
195 |
196 | int dayOfYear = rs.getInt("dayofyear");
197 | assertEquals(53, dayOfYear);
198 |
199 | int hour = rs.getInt("hour");
200 | assertEquals(12,hour);
201 |
202 | int minute = rs.getInt("minute");
203 | assertEquals(0,minute);
204 |
205 | int month = rs.getInt("month");
206 | assertEquals(2,month);
207 |
208 | String monthName = rs.getString("monthname");
209 | assertEquals("FEB", monthName);
210 |
211 | int quarter = rs.getInt("quarter");
212 | assertEquals(1,quarter);
213 |
214 | int second = rs.getInt("second");
215 | assertEquals(0,second);
216 |
217 | int week = rs.getInt("week");
218 | assertEquals(8, week);
219 |
220 | int year = rs.getInt("year");
221 | assertEquals(2015,year);
222 |
223 | rs = stmt.executeQuery("select {fn timestampadd(SQL_TSI_SECOND,30, '2015-02-22 12:00:00'");
224 | assertTrue(rs.next());
225 | Timestamp ts= rs.getTimestamp(1);
226 |
227 | actual.setTime(ts);
228 | assertEquals(30, actual.get(Calendar.SECOND));
229 |
230 | rs = stmt.executeQuery("select {fn timestampadd(SQL_TSI_MINUTE,25, '2015-02-22 12:00:00'");
231 | assertTrue(rs.next());
232 | ts= rs.getTimestamp(1);
233 |
234 | actual.setTime(ts);
235 | assertEquals(25, actual.get(Calendar.MINUTE));
236 |
237 | rs = stmt.executeQuery("select {fn timestampadd(SQL_TSI_HOUR,1, '2015-02-22 12:00:00'");
238 | assertTrue(rs.next());
239 | ts= rs.getTimestamp(1);
240 |
241 | actual.setTime(ts);
242 | assertEquals(13, actual.get(Calendar.HOUR_OF_DAY));
243 |
244 | rs = stmt.executeQuery("select {fn timestampadd(SQL_TSI_DAY,1, '2015-02-22 12:00:00'");
245 | assertTrue(rs.next());
246 | ts= rs.getTimestamp(1);
247 |
248 | actual.setTime(ts);
249 | assertEquals(23, actual.get(Calendar.DAY_OF_MONTH));
250 |
251 | rs = stmt.executeQuery("select {fn timestampadd(SQL_TSI_WEEK,1, '2015-02-22 12:00:00'");
252 | assertTrue(rs.next());
253 | ts= rs.getTimestamp(1);
254 |
255 | actual.setTime(ts);
256 | assertEquals(10, actual.get(Calendar.WEEK_OF_YEAR));
257 |
258 | rs = stmt.executeQuery("select {fn timestampadd(SQL_TSI_MONTH,1, '2015-02-22 12:00:00'");
259 | assertTrue(rs.next());
260 | ts= rs.getTimestamp(1);
261 |
262 | actual.setTime(ts);
263 | assertEquals(2, actual.get(Calendar.MONTH));
264 |
265 |
266 | // second
267 | rs = stmt.executeQuery("select {fn timestampdiff(SQL_TSI_SECOND,{fn now()},{fn timestampadd(SQL_TSI_SECOND,3,{fn now()})})} ");
268 | assertTrue(rs.next());
269 | assertEquals(3,rs.getInt(1));
270 | // MINUTE
271 | rs = stmt.executeQuery("select {fn timestampdiff(SQL_TSI_MINUTE,{fn now()},{fn timestampadd(SQL_TSI_MINUTE,3,{fn now()})})} ");
272 | assertTrue(rs.next());
273 | assertEquals(3,rs.getInt(1));
274 | // HOUR
275 | rs = stmt.executeQuery("select {fn timestampdiff(SQL_TSI_HOUR,{fn now()},{fn timestampadd(SQL_TSI_HOUR,3,{fn now()})})} ");
276 | assertTrue(rs.next());
277 | assertEquals(3,rs.getInt(1));
278 | // day
279 | rs = stmt.executeQuery("select {fn timestampdiff(SQL_TSI_DAY,{fn now()},{fn timestampadd(SQL_TSI_DAY,-3,{fn now()})})} ");
280 | assertTrue(rs.next());
281 | assertEquals(-3,rs.getInt(1));
282 | // WEEK => extract week from interval is not supported by backend
283 | rs = stmt.executeQuery("select {fn timestampdiff(SQL_TSI_WEEK,{fn now()},{fn timestampadd(SQL_TSI_WEEK,3,{fn now()})})} ");
284 | assertTrue(rs.next());
285 | assertEquals(3,rs.getInt(1));
286 | // YEAR
287 | rs = stmt.executeQuery("select {fn timestampdiff(SQL_TSI_YEAR,{fn now()},{fn timestampadd(SQL_TSI_YEAR,3,{fn now()})})} ");
288 | assertTrue(rs.next());
289 | assertEquals(3,rs.getInt(1));
290 |
291 | // QUARTER => backend assume there are 1 quarter even in 270 days...
292 | rs = stmt.executeQuery("select {fn timestampdiff(SQL_TSI_QUARTER,{fn now()},{fn timestampadd(SQL_TSI_QUARTER,3,{fn now()})})} ");
293 | assertTrue(rs.next());
294 | assertEquals(3,rs.getInt(1));
295 |
296 | // MONTH => backend assume there are 0 month in an interval of 92 days...
297 | rs = stmt.executeQuery("select {fn timestampdiff(SQL_TSI_MONTH,{fn now()},{fn timestampadd(SQL_TSI_MONTH,3,{fn now()})})} ");
298 | assertTrue(rs.next());
299 | assertEquals(3,rs.getInt(1));
300 | }
301 |
302 | }
--------------------------------------------------------------------------------
/src/main/java/com/couchbase/jdbc/util/SqlParser.java:
--------------------------------------------------------------------------------
1 | /*
2 | * // Copyright (c) 2015 Couchbase, Inc.
3 | * // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
4 | * // except in compliance with the License. You may obtain a copy of the License at
5 | * // http://www.apache.org/licenses/LICENSE-2.0
6 | * // Unless required by applicable law or agreed to in writing, software distributed under the
7 | * // License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
8 | * // either express or implied. See the License for the specific language governing permissions
9 | * // and limitations under the License.
10 | */
11 |
12 | package com.couchbase.jdbc.util;
13 |
14 | import com.couchbase.jdbc.core.CouchBaseSQLException;
15 | import com.couchbase.jdbc.core.EscapedFunctions;
16 | import com.couchbase.jdbc.core.Parser;
17 |
18 | import java.lang.reflect.InvocationTargetException;
19 | import java.lang.reflect.Method;
20 | import java.sql.SQLException;
21 | import java.util.ArrayList;
22 |
23 | /**
24 | * Created by davec on 2015-03-12.
25 | */
26 | public class SqlParser
27 | {
28 | final ArrayList statementList;
29 | final ArrayList fragmentList;
30 |
31 |
32 | String query;
33 |
34 | public SqlParser(String sql)
35 | {
36 | statementList = new ArrayList();
37 | //noinspection unchecked
38 | fragmentList = new ArrayList(15);
39 | query=sql;
40 | }
41 |
42 | public int getNumFields()
43 | {
44 | return fragmentList.size()-1;
45 | }
46 | public void parse()
47 | {
48 | int parameterIndex = 1;
49 | int fragmentStart = 0;
50 | int inParen = 0;
51 |
52 |
53 | char[] aChars = query.toCharArray();
54 |
55 | for (int i = 0; i < aChars.length; ++i)
56 | {
57 | switch (aChars[i])
58 | {
59 | case '\'': // single-quotes
60 | i = Parser.parseSingleQuotes(aChars, i);
61 | break;
62 |
63 | case '"': // double-quotes
64 | i = Parser.parseDoubleQuotes(aChars, i);
65 | break;
66 |
67 | case '-': // possibly -- style comment
68 | i = Parser.parseLineComment(aChars, i);
69 | break;
70 |
71 | case '/': // possibly /* */ style comment
72 | i = Parser.parseBlockComment(aChars, i);
73 | break;
74 |
75 | case '$': // possibly dollar quote start
76 | i = Parser.parseDollarQuotes(aChars, i);
77 | break;
78 |
79 | case '(':
80 | inParen++;
81 | break;
82 |
83 | case ')':
84 | inParen--;
85 | break;
86 |
87 | case '?':
88 | fragmentList.add(query.substring(fragmentStart, i)+"$"+parameterIndex++);
89 | fragmentStart = i + 1;
90 | break;
91 |
92 | case ';':
93 | if (inParen == 0)
94 | {
95 | fragmentList.add(query.substring(fragmentStart, i));
96 | fragmentStart = i + 1;
97 | if (fragmentList.size() > 1 || fragmentList.get(0).trim().length() > 0)
98 | //noinspection unchecked
99 | statementList.add(fragmentList.toArray(new String[fragmentList.size()]));
100 | fragmentList.clear();
101 | }
102 | break;
103 |
104 | default:
105 | break;
106 | }
107 | }
108 | fragmentList.add(query.substring(fragmentStart));
109 |
110 | }
111 | public String replaceProcessing(String p_sql, boolean replaceProcessingEnabled) throws SQLException
112 | {
113 | if (replaceProcessingEnabled)
114 | {
115 | // Since escape codes can only appear in SQL CODE, we keep track
116 | // of if we enter a string or not.
117 | int len = p_sql.length();
118 | StringBuilder newsql = new StringBuilder(len);
119 | int i=0;
120 | while (i 0)) ORDER BY a
124 | // We can't ending replacing after the extra closing paren
125 | // because that changes a syntax error to a valid query
126 | // that isn't what the user specified.
127 | if (i < len) {
128 | newsql.append(p_sql.charAt(i));
129 | i++;
130 | }
131 | }
132 | return newsql.toString();
133 | }
134 | else
135 | {
136 | return p_sql;
137 | }
138 | }
139 |
140 | private static final short IN_SQLCODE = 0;
141 | private static final short IN_STRING = 1;
142 | private static final short IN_IDENTIFIER = 6;
143 | private static final short BACKSLASH = 2;
144 | private static final short ESC_TIMEDATE = 3;
145 | private static final short ESC_FUNCTION = 4;
146 | private static final short ESC_OUTERJOIN = 5;
147 | private static final short ESC_ESCAPECHAR = 7;
148 |
149 | /*
150 | * parse the given sql from index i, appending it to the gven buffer
151 | * until we hit an unmatched right parentheses or end of string. When
152 | * the stopOnComma flag is set we also stop processing when a comma is
153 | * found in sql text that isn't inside nested parenthesis.
154 | *
155 | * @param p_sql the original query text
156 | * @param i starting position for replacing
157 | * @param newsql where to write the replaced output
158 | * @param stopOnComma should we stop after hitting the first comma in sql text?
159 | * @return the position we stopped processing at
160 | */
161 | protected int parseSql(String p_sql,int i,StringBuilder newsql, boolean stopOnComma ) throws SQLException
162 | {
163 | short state = IN_SQLCODE;
164 | int len = p_sql.length();
165 | int nestedParenthesis=0;
166 | boolean endOfNested=false;
167 |
168 | // because of the ++i loop
169 | i--;
170 | while (!endOfNested && ++i < len)
171 | {
172 | char c = p_sql.charAt(i);
173 | switch (state)
174 | {
175 | case IN_SQLCODE:
176 | if (c == '\'') // start of a string?
177 | state = IN_STRING;
178 | else if (c == '"') // start of a identifer?
179 | state = IN_IDENTIFIER;
180 | else if (c=='(') { // begin nested sql
181 | nestedParenthesis++;
182 | } else if (c==')') { // end of nested sql
183 | nestedParenthesis--;
184 | if (nestedParenthesis<0){
185 | endOfNested=true;
186 | break;
187 | }
188 | } else if (stopOnComma && c==',' && nestedParenthesis==0) {
189 | endOfNested=true;
190 | break;
191 | } else if (c == '{') { // start of an escape code?
192 | if (i + 1 < len)
193 | {
194 | char next = p_sql.charAt(i + 1);
195 | char nextnext = (i + 2 < len) ? p_sql.charAt(i + 2) : '\0';
196 | if (next == 'd' || next == 'D')
197 | {
198 | state = ESC_TIMEDATE;
199 | i++;
200 | newsql.append("DATE ");
201 | break;
202 | }
203 | else if (next == 't' || next == 'T')
204 | {
205 | state = ESC_TIMEDATE;
206 | if (nextnext == 's' || nextnext == 'S'){
207 | // timestamp constant
208 | i+=2;
209 | //newsql.append("TIMESTAMP ");
210 | }else{
211 | // time constant
212 | i++;
213 | //newsql.append("TIME ");
214 | }
215 | break;
216 | }
217 | else if ( next == 'f' || next == 'F' )
218 | {
219 | state = ESC_FUNCTION;
220 | i += (nextnext == 'n' || nextnext == 'N') ? 2 : 1;
221 | break;
222 | }
223 | else if ( next == 'o' || next == 'O' )
224 | {
225 | state = ESC_OUTERJOIN;
226 | i += (nextnext == 'j' || nextnext == 'J') ? 2 : 1;
227 | break;
228 | }
229 | else if ( next == 'e' || next == 'E' )
230 | { // we assume that escape is the only escape sequence beginning with e
231 | state = ESC_ESCAPECHAR;
232 | break;
233 | }
234 | }
235 | }
236 | newsql.append(c);
237 | break;
238 |
239 | case IN_STRING:
240 | if (c == '\'') // end of string?
241 | state = IN_SQLCODE;
242 | else if (c == '\\' ) // a backslash?
243 | state = BACKSLASH;
244 |
245 | newsql.append(c);
246 | break;
247 |
248 | case IN_IDENTIFIER:
249 | if (c == '"') // end of identifier
250 | state = IN_SQLCODE;
251 | newsql.append(c);
252 | break;
253 |
254 | case BACKSLASH:
255 | state = IN_STRING;
256 |
257 | newsql.append(c);
258 | break;
259 |
260 | case ESC_FUNCTION:
261 | // extract function name
262 | String functionName;
263 | int posArgs = p_sql.indexOf('(',i);
264 | if (posArgs!=-1){
265 | functionName=p_sql.substring(i,posArgs).trim();
266 | // extract arguments
267 | i= posArgs+1;// we start the scan after the first (
268 | StringBuilder args=new StringBuilder();
269 | i = parseSql(p_sql,i,args,false);
270 | // translate the function and parse arguments
271 | newsql.append(escapeFunction(functionName,args.toString()));
272 | }
273 | // go to the end of the function copying anything found
274 | i++;
275 | while (iResultSet object.
31 | *
32 | * @return the number of columns
33 | * @throws java.sql.SQLException if a database access error occurs
34 | */
35 | @Override
36 | public int getColumnCount() throws SQLException
37 | {
38 | return resultSet.fields.size();
39 | }
40 |
41 | /**
42 | * Indicates whether the designated column is automatically numbered.
43 | *
44 | * @param column the first column is 1, the second is 2, ...
45 | * @return true if so; false otherwise
46 | * @throws java.sql.SQLException if a database access error occurs
47 | */
48 | @Override
49 | public boolean isAutoIncrement(int column) throws SQLException
50 | {
51 | return false;
52 | }
53 |
54 | /**
55 | * Indicates whether a column's case matters.
56 | *
57 | * @param column the first column is 1, the second is 2, ...
58 | * @return true if so; false otherwise
59 | * @throws java.sql.SQLException if a database access error occurs
60 | */
61 | @Override
62 | public boolean isCaseSensitive(int column) throws SQLException
63 | {
64 | return false;
65 | }
66 |
67 | /**
68 | * Indicates whether the designated column can be used in a where clause.
69 | *
70 | * @param column the first column is 1, the second is 2, ...
71 | * @return true if so; false otherwise
72 | * @throws java.sql.SQLException if a database access error occurs
73 | */
74 | @Override
75 | public boolean isSearchable(int column) throws SQLException
76 | {
77 | return false;
78 | }
79 |
80 | /**
81 | * Indicates whether the designated column is a cash value.
82 | *
83 | * @param column the first column is 1, the second is 2, ...
84 | * @return true if so; false otherwise
85 | * @throws java.sql.SQLException if a database access error occurs
86 | */
87 | @Override
88 | public boolean isCurrency(int column) throws SQLException
89 | {
90 | return false;
91 | }
92 |
93 | /**
94 | * Indicates the nullability of values in the designated column.
95 | *
96 | * @param column the first column is 1, the second is 2, ...
97 | * @return the nullability status of the given column; one of columnNoNulls,
98 | * columnNullable or columnNullableUnknown
99 | * @throws java.sql.SQLException if a database access error occurs
100 | */
101 | @Override
102 | public int isNullable(int column) throws SQLException
103 | {
104 | return columnNullable;
105 | }
106 |
107 | /**
108 | * Indicates whether values in the designated column are signed numbers.
109 | *
110 | * @param column the first column is 1, the second is 2, ...
111 | * @return true if so; false otherwise
112 | * @throws java.sql.SQLException if a database access error occurs
113 | */
114 | @Override
115 | public boolean isSigned(int column) throws SQLException
116 | {
117 | return false;
118 | }
119 |
120 | /**
121 | * Indicates the designated column's normal maximum width in characters.
122 | *
123 | * @param column the first column is 1, the second is 2, ...
124 | * @return the normal maximum number of characters allowed as the width
125 | * of the designated column
126 | * @throws java.sql.SQLException if a database access error occurs
127 | */
128 | @Override
129 | public int getColumnDisplaySize(int column) throws SQLException
130 | {
131 | return 0;
132 | }
133 |
134 | /**
135 | * Gets the designated column's suggested title for use in printouts and
136 | * displays. The suggested title is usually specified by the SQL AS
137 | * clause. If a SQL AS is not specified, the value returned from
138 | * getColumnLabel will be the same as the value returned by the
139 | * getColumnName method.
140 | *
141 | * @param column the first column is 1, the second is 2, ...
142 | * @return the suggested column title
143 | * @throws java.sql.SQLException if a database access error occurs
144 | */
145 | @Override
146 | public String getColumnLabel(int column) throws SQLException
147 | {
148 |
149 | return resultSet.getField(column).getName();
150 | }
151 |
152 | /**
153 | * Get the designated column's name.
154 | *
155 | * @param column the first column is 1, the second is 2, ...
156 | * @return column name
157 | * @throws java.sql.SQLException if a database access error occurs
158 | */
159 | @Override
160 | public String getColumnName(int column) throws SQLException
161 | {
162 | return resultSet.getField(column).getName();
163 | }
164 |
165 | /**
166 | * Get the designated column's table's schema.
167 | *
168 | * @param column the first column is 1, the second is 2, ...
169 | * @return schema name or "" if not applicable
170 | * @throws java.sql.SQLException if a database access error occurs
171 | */
172 | @Override
173 | public String getSchemaName(int column) throws SQLException
174 | {
175 | return null;
176 | }
177 |
178 | /**
179 | * Get the designated column's specified column size.
180 | * For numeric data, this is the maximum precision. For character data, this is the length in characters.
181 | * For datetime datatypes, this is the length in characters of the String representation (assuming the
182 | * maximum allowed precision of the fractional seconds component). For binary data, this is the length in bytes. For the ROWID datatype,
183 | * this is the length in bytes. 0 is returned for data types where the
184 | * column size is not applicable.
185 | *
186 | * @param column the first column is 1, the second is 2, ...
187 | * @return precision
188 | * @throws java.sql.SQLException if a database access error occurs
189 | */
190 | @Override
191 | public int getPrecision(int column) throws SQLException
192 | {
193 | return 0;
194 | }
195 |
196 | /**
197 | * Gets the designated column's number of digits to right of the decimal point.
198 | * 0 is returned for data types where the scale is not applicable.
199 | *
200 | * @param column the first column is 1, the second is 2, ...
201 | * @return scale
202 | * @throws java.sql.SQLException if a database access error occurs
203 | */
204 | @Override
205 | public int getScale(int column) throws SQLException
206 | {
207 | return 0;
208 | }
209 |
210 | /**
211 | * Gets the designated column's table name.
212 | *
213 | * @param column the first column is 1, the second is 2, ...
214 | * @return table name or "" if not applicable
215 | * @throws java.sql.SQLException if a database access error occurs
216 | */
217 | @Override
218 | public String getTableName(int column) throws SQLException
219 | {
220 | return null;
221 | }
222 |
223 | /**
224 | * Gets the designated column's table's catalog name.
225 | *
226 | * @param column the first column is 1, the second is 2, ...
227 | * @return the name of the catalog for the table in which the given column
228 | * appears or "" if not applicable
229 | * @throws java.sql.SQLException if a database access error occurs
230 | */
231 | @Override
232 | public String getCatalogName(int column) throws SQLException
233 | {
234 | return null;
235 | }
236 |
237 | /**
238 | * Retrieves the designated column's SQL type.
239 | *
240 | * @param column the first column is 1, the second is 2, ...
241 | * @return SQL type from java.sql.Types
242 | * @throws java.sql.SQLException if a database access error occurs
243 | * @see java.sql.Types
244 | */
245 | @Override
246 | public int getColumnType(int column) throws SQLException
247 | {
248 | return resultSet.getField(column).getSqlType();
249 | }
250 |
251 | /**
252 | * Retrieves the designated column's database-specific type name.
253 | *
254 | * @param column the first column is 1, the second is 2, ...
255 | * @return type name used by the database. If the column type is
256 | * a user-defined type, then a fully-qualified type name is returned.
257 | * @throws java.sql.SQLException if a database access error occurs
258 | */
259 | @Override
260 | public String getColumnTypeName(int column) throws SQLException
261 | {
262 | return resultSet.getField(column).getType();
263 | }
264 |
265 | /**
266 | * Indicates whether the designated column is definitely not writable.
267 | *
268 | * @param column the first column is 1, the second is 2, ...
269 | * @return true if so; false otherwise
270 | * @throws java.sql.SQLException if a database access error occurs
271 | */
272 | @Override
273 | public boolean isReadOnly(int column) throws SQLException
274 | {
275 | return false;
276 | }
277 |
278 | /**
279 | * Indicates whether it is possible for a write on the designated column to succeed.
280 | *
281 | * @param column the first column is 1, the second is 2, ...
282 | * @return true if so; false otherwise
283 | * @throws java.sql.SQLException if a database access error occurs
284 | */
285 | @Override
286 | public boolean isWritable(int column) throws SQLException
287 | {
288 | return false;
289 | }
290 |
291 | /**
292 | * Indicates whether a write on the designated column will definitely succeed.
293 | *
294 | * @param column the first column is 1, the second is 2, ...
295 | * @return true if so; false otherwise
296 | * @throws java.sql.SQLException if a database access error occurs
297 | */
298 | @Override
299 | public boolean isDefinitelyWritable(int column) throws SQLException
300 | {
301 | return false;
302 | }
303 |
304 | /**
305 | * Returns the fully-qualified name of the Java class whose instances
306 | * are manufactured if the method ResultSet.getObject
307 | * is called to retrieve a value
308 | * from the column. ResultSet.getObject may return a subclass of the
309 | * class returned by this method.
310 | *
311 | * @param column the first column is 1, the second is 2, ...
312 | * @return the fully-qualified name of the class in the Java programming
313 | * language that would be used by the method
314 | * ResultSet.getObject to retrieve the value in the specified
315 | * column. This is the class name used for custom mapping.
316 | * @throws java.sql.SQLException if a database access error occurs
317 | * @since 1.2
318 | */
319 | @Override
320 | public String getColumnClassName(int column) throws SQLException
321 | {
322 | return null;
323 | }
324 |
325 | /**
326 | * Returns an object that implements the given interface to allow access to
327 | * non-standard methods, or standard methods not exposed by the proxy.
328 | *
329 | * If the receiver implements the interface then the result is the receiver
330 | * or a proxy for the receiver. If the receiver is a wrapper
331 | * and the wrapped object implements the interface then the result is the
332 | * wrapped object or a proxy for the wrapped object. Otherwise return the
333 | * the result of calling unwrap recursively on the wrapped object
334 | * or a proxy for that result. If the receiver is not a
335 | * wrapper and does not implement the interface, then an SQLException is thrown.
336 | *
337 | * @param iface A Class defining an interface that the result must implement.
338 | * @return an object that implements the interface. May be a proxy for the actual implementing object.
339 | * @throws java.sql.SQLException If no object found that implements the interface
340 | * @since 1.6
341 | */
342 | @Override
343 | public T unwrap(Class iface) throws SQLException
344 | {
345 | return null;
346 | }
347 |
348 | /**
349 | * Returns true if this either implements the interface argument or is directly or indirectly a wrapper
350 | * for an object that does. Returns false otherwise. If this implements the interface then return true,
351 | * else if this is a wrapper then return the result of recursively calling isWrapperFor on the wrapped
352 | * object. If this does not implement the interface and is not a wrapper, return false.
353 | * This method should be implemented as a low-cost operation compared to unwrap so that
354 | * callers can use this method to avoid expensive unwrap calls that may fail. If this method
355 | * returns true then calling unwrap with the same argument should succeed.
356 | *
357 | * @param iface a Class defining an interface.
358 | * @return true if this implements the interface or directly or indirectly wraps an object that does.
359 | * @throws java.sql.SQLException if an error occurs while determining whether this is a wrapper
360 | * for an object with the given interface.
361 | * @since 1.6
362 | */
363 | @Override
364 | public boolean isWrapperFor(Class> iface) throws SQLException
365 | {
366 | return false;
367 | }
368 | }
369 |
--------------------------------------------------------------------------------