Merge lp:~pbeaman/akiban-server/direct-updates-2 into lp:~akiban-technologies/akiban-server/trunk

Proposed by Peter Beaman
Status: Merged
Approved by: Peter Beaman
Approved revision: 2637
Merged at revision: 2645
Proposed branch: lp:~pbeaman/akiban-server/direct-updates-2
Merge into: lp:~akiban-technologies/akiban-server/trunk
Prerequisite: lp:~pbeaman/akiban-server/fix-direct-rest-bugs
Diff against target: 2401 lines (+1402/-219)
37 files modified
src/main/java/com/akiban/direct/AbstractDirectObject.java (+497/-46)
src/main/java/com/akiban/direct/ClassBuilder.java (+71/-19)
src/main/java/com/akiban/direct/ClassObjectWriter.java (+3/-18)
src/main/java/com/akiban/direct/ClassSourceWriter.java (+8/-34)
src/main/java/com/akiban/direct/ClassXRefWriter.java (+5/-5)
src/main/java/com/akiban/direct/Direct.java (+16/-6)
src/main/java/com/akiban/direct/DirectColumn.java (+35/-0)
src/main/java/com/akiban/direct/DirectIterable.java (+2/-1)
src/main/java/com/akiban/direct/DirectIterableImpl.java (+30/-20)
src/main/java/com/akiban/rest/RestResponseBuilder.java (+6/-3)
src/main/java/com/akiban/rest/resources/DirectResource.java (+0/-8)
src/main/java/com/akiban/server/error/DirectEndpointNotFoundException.java (+25/-0)
src/main/java/com/akiban/server/error/DirectTransactionFailedException.java (+25/-0)
src/main/java/com/akiban/server/error/ErrorCode.java (+2/-0)
src/main/java/com/akiban/server/service/restdml/DirectServiceImpl.java (+10/-7)
src/main/java/com/akiban/server/service/restdml/EndpointMetadata.java (+4/-4)
src/main/java/com/akiban/server/types3/TClassBase.java (+2/-0)
src/main/java/com/akiban/sql/embedded/JDBCResultSet.java (+3/-2)
src/main/resources/com/akiban/server/error/error_code.properties (+2/-0)
src/test/java/com/akiban/direct/COIDirectClasses.java (+288/-0)
src/test/java/com/akiban/direct/DirectUpdateIT.java (+215/-0)
src/test/java/com/akiban/rest/RestServiceScriptsIT.java (+25/-7)
src/test/java/com/akiban/server/service/restdml/DirectServiceTest.java (+0/-2)
src/test/resources/com/akiban/direct/addresses.dat (+2/-0)
src/test/resources/com/akiban/direct/customers.dat (+4/-0)
src/test/resources/com/akiban/direct/items.dat (+3/-0)
src/test/resources/com/akiban/direct/orders.dat (+3/-0)
src/test/resources/com/akiban/direct/schema.ddl (+32/-0)
src/test/resources/com/akiban/rest/direct/direct-create.body (+1/-13)
src/test/resources/com/akiban/rest/direct/direct-create.expected (+1/-1)
src/test/resources/com/akiban/rest/direct/direct-demo.js (+3/-1)
src/test/resources/com/akiban/rest/direct/direct-update.body (+35/-0)
src/test/resources/com/akiban/rest/direct/direct-xref.expected (+0/-1)
src/test/resources/com/akiban/rest/direct/test-basic-direct-functions.script (+3/-4)
src/test/resources/com/akiban/rest/direct/test-direct-registration-errors.script (+2/-2)
src/test/resources/com/akiban/rest/direct/test-invalid-endpoint-errors.script (+8/-15)
src/test/resources/com/akiban/rest/direct/test-updates.script (+31/-0)
To merge this branch: bzr merge lp:~pbeaman/akiban-server/direct-updates-2
Reviewer Review Type Date Requested Status
Akiban Build User Needs Fixing
Mike McMahon Approve
Review via email: mp+160248@code.launchpad.net

Description of the change

Replaces lp:~pbeaman/akiban-server/direct-updates. This branch adds bug fixes from lp:~pbeaman/akiban-server/fix-direct-rest-bugs which is now a prerequisite. This branch also utilizes RestServiceScriptIT to test update functions through REST calls.

Description from original proposal:

Add new functionality so that Akiban Direct can insert and update entities. Note that this branch does not delete rows - another branch will add support for delete.

Add new test class com.akiban.direct.DirectUpdateIT. Much of the new functionality is exercised there.

Primary changes are in com.akiban.direct.AbstractDirectObject which now supplies methods to set and store updated fields, to invoke SQL INSERT and UPDATE statements, to hold metadata about the underlying table, etc. Corresponding changes in the classes written by com.akiban.direct.ClassBuilder utilize these new methods.

Also now supported: com.akiban.direct.DirectIterable#newInstance (allows creation of a new member of a collection, e.g., a new Customer), and com.akiban.direct.DirectObject#copy() (creates a close of a direct object which is detached from the original object's cursor).

Add two potentially controversial methods to JDBCResultSet: a protected copy-constructor and a protected method needed to stuff the private values field into a copied AbstractDataObject. I would like this methods to be invisible, but at least they don't need to be public. Suggestions for less bizarre plumbing would be welcome.

To post a comment you must log in.
Revision history for this message
Mike McMahon (mmcm) wrote :

JDBCResultSet already has a getEntity method that knows some details of AbstractDirectObject. So, rather than having each know the other's insides, I think it would be cleaner to have a new method on JDBCResultSet that returns a clone as a AbstractDirectObject or an argument to getEntity that says to freeze the values or something there. copyInstance will still needs to copy pending updates into the result.

It isn't okay to just keep the same Row instance in something intended to be a static copy. That's likely to change when the Cursor is advanced. That Project does not share today is a performance bug in the operator. A ShareHolder is needed and perhaps conversion to ImmutableRow, since bindings from which calculations are done could change, too, if arbitrary projections are allowed.

Revision history for this message
Mike McMahon (mmcm) wrote :

It seems that all AbstractDirectObject really wants is a ServerJavaValues. So perhaps it would be simplest to give that out via JDBCResultSet.unwrap(). The other method, DirectResultSet.hasRow(), only serves to change an error message from "Past end" to "No more rows." This would eliminate a lot of forward knowledge of Direct.

It would then seem perfectly reasonable for ServerJavaValues (or a related interface) to have a method to "freeze" values for the purpose of copy. This could be unimplemented or a noop for most implementers and do what's needed for JDBCResultSet.

Revision history for this message
Peter Beaman (pbeaman) wrote :

Yes, AbstractDirectObject merely wants a way to produce column values from some kind of row-like object.

So I think the best option is to remove the copy() method from ADO - I think that gets rid of the ugly copy constructor, etc. The hypothetical use case was to allow a procedure to hold copies of different instances found within a loop - for example, to traverse all the members of Salary and to hold copies of Salary objects representing the minimum and maximum values. However, I have not yet written any code that requires this, and when that case becomes clearly necessary we can figure out how we want to clone a row.

The code as written is broken because the JDBCResultSet's copy constructor does not construct a copy of the actual Row, as you pointed out. To satisfy my hypothetical use case would require doing so somewhere. Freezing the Row would not allow the original object to be reused in the loop, which is part of the (unwritten and unreviewed) contract.

The new code I added to populate a ResultSet for an instance created through save() probably does not need to make a copy - to my knowledge there is no other reference to the JDBCResultSet acquired from executing the prepared INSERT statement with RETURNING clause, and therefore its single-row result should be stable. Please correct me if I'm wrong.

Revision history for this message
Peter Beaman (pbeaman) wrote :

The branch is updated to reflect these changes; the copy() method is gone.

Also changed a couple of throw statements in DirectServiceImpl to throw
newly added InvalidOperationException instances rather than
WebApplicationException. This makes handling in
RestResponseBuilder.wrapException cleaner and results in the correct status
being returned.

On Wed, Apr 24, 2013 at 10:40 AM, Peter Beaman <email address hidden> wrote:

> Yes, AbstractDirectObject merely wants a way to produce column values from
> some kind of row-like object.
>
> So I think the best option is to remove the copy() method from ADO - I
> think that gets rid of the ugly copy constructor, etc. The hypothetical
> use case was to allow a procedure to hold copies of different instances
> found within a loop - for example, to traverse all the members of Salary
> and to hold copies of Salary objects representing the minimum and maximum
> values. However, I have not yet written any code that requires this, and
> when that case becomes clearly necessary we can figure out how we want to
> clone a row.
>
> The code as written is broken because the JDBCResultSet's copy constructor
> does not construct a copy of the actual Row, as you pointed out. To
> satisfy my hypothetical use case would require doing so somewhere. Freezing
> the Row would not allow the original object to be reused in the loop, which
> is part of the (unwritten and unreviewed) contract.
>
> The new code I added to populate a ResultSet for an instance created
> through save() probably does not need to make a copy - to my knowledge
> there is no other reference to the JDBCResultSet acquired from executing
> the prepared INSERT statement with RETURNING clause, and therefore its
> single-row result should be stable. Please correct me if I'm wrong.
>
>
> --
>
> https://code.launchpad.net/~pbeaman/akiban-server/direct-updates-2/+merge/160248
> You are the owner of lp:~pbeaman/akiban-server/direct-updates-2.
>

Revision history for this message
Peter Beaman (pbeaman) wrote :

Done. Also replaced erroneous use of WebApplicationException in DirectServiceImpl. Simplifies and cleans up RestResponseBuilder.

Revision history for this message
Mike McMahon (mmcm) wrote :

The current plan is to have a new branch for the next phase of this work. So, since I don't see anything dangerous or disruptive outside of Direct in this, approving to keep the number of pending changes down.

review: Approve
Revision history for this message
Akiban Build User (build-akiban) wrote :

There were 2 failures during build/test:

* job server-build failed at build number 4003: http://172.16.20.104:8080/job/server-build/4003/

* view must-pass failed: server-build is yellow

review: Needs Fixing
2637. By Peter Beaman

Fix broken toString method

Revision history for this message
Peter Beaman (pbeaman) wrote :

Forgot to make a compensating change in EndpointMetadata.toString().

Have pushed that change and will re-Approve once the diff is done.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/main/java/com/akiban/direct/AbstractDirectObject.java'
2--- src/main/java/com/akiban/direct/AbstractDirectObject.java 2013-03-22 20:05:57 +0000
3+++ src/main/java/com/akiban/direct/AbstractDirectObject.java 2013-04-26 02:16:29 +0000
4@@ -19,119 +19,570 @@
5
6 import java.math.BigDecimal;
7 import java.math.BigInteger;
8+import java.sql.Connection;
9 import java.sql.Date;
10+import java.sql.PreparedStatement;
11+import java.sql.SQLException;
12 import java.sql.Time;
13 import java.sql.Timestamp;
14-
15-import com.akiban.sql.server.ServerJavaValues;
16-
17-public class AbstractDirectObject implements DirectObject {
18-
19- private ServerJavaValues values;
20+import java.util.Arrays;
21+import java.util.BitSet;
22+import java.util.HashMap;
23+import java.util.Map;
24+import java.util.WeakHashMap;
25+
26+public abstract class AbstractDirectObject implements DirectObject {
27+
28+ private final static Map<Connection, Map<BitSet, PreparedStatement>> updateStatementCache = new WeakHashMap<>();
29+ private final static Map<Connection, Map<BitSet, PreparedStatement>> insertStatementCache = new WeakHashMap<>();
30+
31+ /*
32+ * 1. schema_name 2. tableName 3. comma-separated list of column names, 4.
33+ * comma-separated list of '?' symbols.
34+ */
35+ private final static String INSERT_STATEMENT = "insert into \"%s\".\"%s\" (%s) values (%s) returning *";
36+
37+ /*
38+ * 1. schema name 2. table name 3. comma-separated list of column_name=?
39+ * pairs 4. predicate: pkcolumn=?, ...
40+ */
41+ private final static String UPDATE_STATEMENT = "update \"%s\".\"%s\" set %s where %s";
42+
43+ private final static Object NOT_SET = new Object() {
44+ @Override
45+ public String toString() {
46+ return "NOT_SET";
47+ }
48+ };
49+
50+ private static Column[] columns;
51+ private static String schemaName;
52+ private static String tableName;
53+
54+ protected static class Column implements DirectColumn {
55+
56+ final int columnIndex;
57+ final String columnName;
58+ final String propertyName;
59+ final String propertyType;
60+ final int primaryKeyFieldIndex;
61+ final int parentJoinFieldIndex;
62+
63+ protected Column(final int columnIndex, final String columnName, final String propertyName,
64+ final String propertyType, final int primaryKeyFieldIndex, final int parentJoinFieldIndex) {
65+ this.columnIndex = columnIndex;
66+ this.columnName = columnName;
67+ this.propertyName = propertyName;
68+ this.propertyType = propertyType;
69+ this.primaryKeyFieldIndex = primaryKeyFieldIndex;
70+ this.parentJoinFieldIndex = parentJoinFieldIndex;
71+ }
72+
73+ public int getColumnIndex() {
74+ return columnIndex;
75+ }
76+
77+ public String getColumnName() {
78+ return columnName;
79+ }
80+
81+ public String getPropertyName() {
82+ return propertyName;
83+ }
84+
85+ public String getPropertyType() {
86+ return propertyType;
87+ }
88+ }
89+
90+ /**
91+ * Static initializer of subclass passes a string declaring the columns.
92+ * Format is columnName:propertyName:propertyType:primaryKeyFieldIndex:
93+ * parentjoinField,...
94+ *
95+ * @param columnSpecs
96+ */
97+ protected static void __init(final String sName, final String tName, final String columnSpecs) {
98+ try {
99+ schemaName = sName;
100+ tableName = tName;
101+ String[] columnArray = columnSpecs.split(",");
102+ columns = new Column[columnArray.length];
103+ for (int index = 0; index < columnArray.length; index++) {
104+ String[] v = columnArray[index].split(":");
105+ columns[index] = new Column(index, v[0], v[1], v[2], Integer.parseInt(v[3]), Integer.parseInt(v[4]));
106+ }
107+ } catch (Exception e) {
108+ throw new DirectException(e);
109+ }
110+ }
111+
112+ private Object[] updates;
113 private DirectResultSet rs;
114
115- public void setResults(ServerJavaValues values, DirectResultSet rs) {
116- this.values = values;
117+ public void setResults(DirectResultSet rs) {
118+ if (updates != null) {
119+ throw new IllegalStateException("Updates not saved: " + updates);
120+ }
121 this.rs = rs;
122- }
123-
124- public ServerJavaValues values() {
125- if (rs.hasRow()) {
126- return values;
127- }
128- throw new IllegalStateException("No more rows");
129- }
130-
131- public void save() {
132- // TODO
133+ updates = null;
134+ }
135+
136+ private Object[] updates() {
137+ if (updates == null) {
138+ updates = new Object[columns.length];
139+ Arrays.fill(updates, NOT_SET);
140+ }
141+ return updates;
142+ }
143+
144+ /**
145+ * Instantiates (update) values for parent join fields. This method is
146+ * invoked from {@link DirectIterableImpl#newInstance()}.
147+ *
148+ * @param parent
149+ */
150+ void populateJoinFields(DirectObject parent) {
151+ if (parent instanceof AbstractDirectObject) {
152+ AbstractDirectObject ado = (AbstractDirectObject) parent;
153+ if (parent != null) {
154+ for (int index = 0; index < columns.length; index++) {
155+ Column c = columns[index];
156+ if (c.parentJoinFieldIndex >= 0) {
157+ updates()[index] = ado.__getObject(c.parentJoinFieldIndex);
158+ }
159+ }
160+ }
161+ }
162 }
163
164 protected boolean __getBOOL(int p) {
165- return values().getBoolean(p);
166+ if (updates == null || updates[p] == NOT_SET) {
167+ try {
168+ return rs.getBoolean(p + 1);
169+ } catch (SQLException e) {
170+ throw (RuntimeException)e.getCause();
171+ }
172+ } else {
173+ return (boolean) updates[p];
174+ }
175+ }
176+
177+ protected void __setBOOL(int p, boolean v) {
178+ updates()[p] = v;
179 }
180
181 protected Date __getDATE(int p) {
182- return values().getDate(p);
183- }
184-
185- protected Date __getDATETIME(int p) {
186- return values().getDate(p);
187+ if (updates == null || updates[p] == NOT_SET) {
188+ try {
189+ return rs.getDate(p + 1);
190+ } catch (SQLException e) {
191+ throw (RuntimeException)e.getCause();
192+ }
193+ } else {
194+ return (Date) updates[p];
195+ }
196+ }
197+
198+ protected void __setDATE(int p, Date v) {
199+ updates()[p] = v;
200+ }
201+
202+ protected Timestamp __getDATETIME(int p) {
203+ if (updates == null || updates[p] == NOT_SET) {
204+ try {
205+ return rs.getTimestamp(p + 1);
206+ } catch (SQLException e) {
207+ throw (RuntimeException)e.getCause();
208+ }
209+ } else {
210+ return (Timestamp) updates[p];
211+ }
212+ }
213+
214+ protected void __setDATETIME(int p, Timestamp v) {
215+ updates()[p] = v;
216 }
217
218 protected BigDecimal __getDECIMAL(int p) {
219- return values().getBigDecimal(p);
220+ if (updates == null || updates[p] == NOT_SET) {
221+ try {
222+ return rs.getBigDecimal(p + 1);
223+ } catch (SQLException e) {
224+ throw (RuntimeException)e.getCause();
225+ }
226+ } else {
227+ return (BigDecimal) updates[p];
228+ }
229+ }
230+
231+ protected void __setDECIMAL(int p, BigDecimal v) {
232+ updates()[p] = v;
233 }
234
235 protected double __getDOUBLE(int p) {
236- return values().getDouble(p);
237+ if (updates == null || updates[p] == NOT_SET) {
238+ try {
239+ return rs.getDouble(p + 1);
240+ } catch (SQLException e) {
241+ throw (RuntimeException)e.getCause();
242+ }
243+ } else {
244+ return (double) updates[p];
245+ }
246+ }
247+
248+ protected void __setDOUBLE(int p, double v) {
249+ updates()[p] = v;
250 }
251
252 protected float __getFLOAT(int p) {
253- return values().getFloat(p);
254+ if (updates == null || updates[p] == NOT_SET) {
255+ try {
256+ return rs.getFloat(p + 1);
257+ } catch (SQLException e) {
258+ throw (RuntimeException)e.getCause();
259+ }
260+ } else {
261+ return (float) updates[p];
262+ }
263+ }
264+
265+ protected void __setFLOAT(int p, float v) {
266+ updates()[p] = v;
267 }
268
269 protected int __getINT(int p) {
270- return values().getInt(p);
271+ if (updates == null || updates[p] == NOT_SET) {
272+ try {
273+ return rs.getInt(p + 1);
274+ } catch (SQLException e) {
275+ throw (RuntimeException)e.getCause();
276+ }
277+ } else {
278+ return (int) updates[p];
279+ }
280+ }
281+
282+ protected void __setINT(int p, int v) {
283+ updates()[p] = v;
284 }
285
286 protected int __getINTERVAL_MILLIS(int p) {
287- throw new UnsupportedOperationException("Don't know how to convert a INTERVAL_MILLIS from a ValueSource");
288+ throw new UnsupportedOperationException("Don't know how to convert an INTERVAL_MILLIS from a ValueSource");
289+ }
290+
291+ protected void __setINTERVAL_MILLIS(int p, int v) {
292+ throw new UnsupportedOperationException("Don't know how to store an INTERVAL_MILLIS");
293 }
294
295 protected int __getINTERVAL_MONTH(int p) {
296- throw new UnsupportedOperationException("Don't know how to convert a INTERVAL_MONTH from a ValueSource");
297+ throw new UnsupportedOperationException("Don't know how to convert an INTERVAL_MONTH from a ValueSource");
298+ }
299+
300+ protected void __setINTERVAL_MONTH(int p, int v) {
301+ throw new UnsupportedOperationException("Don't know how to store an INTERVAL_MONTH");
302 }
303
304 protected long __getLONG(int p) {
305- return values().getLong(p);
306- }
307-
308- protected Object __getNULL(int p) {
309- throw new UnsupportedOperationException("Don't know how to convert a NULL from a ValueSource");
310- }
311-
312- protected Object __getRESULT_SET(int p) {
313- throw new UnsupportedOperationException("Don't know how to convert a RESULT_SET from a ValueSource");
314+ if (updates == null || updates[p] == NOT_SET) {
315+ try {
316+ return rs.getLong(p + 1);
317+ } catch (SQLException e) {
318+ throw (RuntimeException)e.getCause();
319+ }
320+ } else {
321+ return (long) updates[p];
322+ }
323+ }
324+
325+ protected void __setLONG(int p, long v) {
326+ updates()[p] = v;
327 }
328
329 protected String __getTEXT(int p) {
330- return values().getString(p);
331+ if (updates == null || updates[p] == NOT_SET) {
332+ try {
333+ return rs.getString(p + 1);
334+ } catch (SQLException e) {
335+ throw (RuntimeException)e.getCause();
336+ }
337+ } else {
338+ return (String) updates[p];
339+ }
340+ }
341+
342+ protected void __setTEXT(int p, String v) {
343+ updates()[p] = v;
344 }
345
346 protected Time __getTIME(int p) {
347- return values().getTime(p);
348+ if (updates == null || updates[p] == NOT_SET) {
349+ try {
350+ return rs.getTime(p + 1);
351+ } catch (SQLException e) {
352+ throw (RuntimeException)e.getCause();
353+ }
354+ } else {
355+ return (Time) updates[p];
356+ }
357+ }
358+
359+ protected void __setTIME(int p, Time v) {
360+ updates()[p] = v;
361 }
362
363 protected Timestamp __getTIMESTAMP(int p) {
364- return values().getTimestamp(p);
365+ if (updates == null || updates[p] == NOT_SET) {
366+ try {
367+ return rs.getTimestamp(p + 1);
368+ } catch (SQLException e) {
369+ throw (RuntimeException)e.getCause();
370+ }
371+ } else {
372+ return (Timestamp) updates[p];
373+ }
374+ }
375+
376+ protected Object __getObject(int p) {
377+ if (updates == null || updates[p] == NOT_SET) {
378+ try {
379+ return rs.getObject(p + 1);
380+ } catch (SQLException e) {
381+ throw (RuntimeException)e.getCause();
382+ }
383+ } else {
384+ return updates[p];
385+ }
386+ }
387+
388+ protected void __setTIMESTAMP(int p, Timestamp v) {
389+ updates()[p] = v;
390 }
391
392 protected long __getU_INT(int p) {
393 throw new UnsupportedOperationException("Don't know how to convert a U_INT from a ValueSource");
394 }
395
396+ protected void __setU_INT(int p, long v) {
397+ throw new UnsupportedOperationException("Don't know how to store a U_INT");
398+ }
399+
400 protected BigInteger __getU_BIGINT(int p) {
401 throw new UnsupportedOperationException("Don't know how to convert a U_BIGINT from a ValueSource");
402 }
403
404+ protected void __setU_BIGINT(int p, BigInteger v) {
405+ throw new UnsupportedOperationException("Don't know how to store a U_BIGINT");
406+ }
407+
408 protected BigDecimal __getU_DOUBLE(int p) {
409 throw new UnsupportedOperationException("Don't know how to convert a U_DOUBLE from a ValueSource");
410 }
411
412+ protected void __setU_DOUBLE(int p, BigDecimal v) {
413+ throw new UnsupportedOperationException("Don't know how to store a U_DOUBLE");
414+ }
415+
416 protected double __getU_FLOAT(int p) {
417 throw new UnsupportedOperationException("Don't know how to convert a U_FLOAT from a ValueSource");
418 }
419
420+ protected void __setU_FLOAT(int p, double v) {
421+ throw new UnsupportedOperationException("Don't know how to store a U_FLOAT");
422+ }
423+
424 protected String __getVARCHAR(int p) {
425- return values().getString(p);
426+ if (updates == null || updates[p] == NOT_SET) {
427+ try {
428+ return rs.getString(p + 1);
429+ } catch (SQLException e) {
430+ throw (RuntimeException)e.getCause();
431+ }
432+ } else {
433+ return (String) updates[p];
434+ }
435+ }
436+
437+ protected void __setVARCHAR(int p, String v) {
438+ updates()[p] = v;
439 }
440
441 protected byte[] __getVARBINARY(int p) {
442- return values().getBytes(p);
443+ if (updates == null || updates[p] == NOT_SET) {
444+ try {
445+ return rs.getBytes(p + 1);
446+ } catch (SQLException e) {
447+ throw (RuntimeException)e.getCause();
448+ }
449+ } else {
450+ return (byte[]) updates[p];
451+ }
452+ }
453+
454+ protected void __setVARBINARY(int p, byte[] v) {
455+ updates()[p] = v;
456 }
457
458 protected int __getYEAR(int p) {
459- return values().getInt(p);
460+ if (updates == null || updates[p] == NOT_SET) {
461+ try {
462+ return rs.getInt(p + 1);
463+ } catch (SQLException e) {
464+ throw (RuntimeException)e.getCause();
465+ }
466+ } else {
467+ return (int) updates[p];
468+ }
469+ }
470+
471+ protected void __setYEAR(int p, int v) {
472+ updates()[p] = v;
473+ }
474+
475+ /**
476+ * Issue either an INSERT or an UPDATE statement depending on whether this
477+ * instance is bound to a result set.
478+ */
479+ public void save() {
480+ try {
481+ /*
482+ * If rs == null then this instance was created via the
483+ * DirectIterable#newInstance method and the intention is to INSERT
484+ * it. If rs is not null, then this instance was selected from an
485+ * existing table and the intention is to UPDATE it.
486+ */
487+ if (rs == null) {
488+ PreparedStatement stmt = __insertStatement();
489+ stmt.execute();
490+ rs = (DirectResultSet)stmt.getGeneratedKeys();
491+ try {
492+ rs.next();
493+ } catch (SQLException e) {
494+ throw new DirectException(e);
495+ }
496+ updates = null;
497+ } else {
498+ PreparedStatement stmt = __updateStatement();
499+ stmt.execute();
500+ updates = null;
501+ }
502+
503+ } catch (SQLException e) {
504+ throw new DirectException(e);
505+ }
506+ }
507+
508+ private PreparedStatement __insertStatement() throws SQLException {
509+ assert updates != null : "No updates to save";
510+ BitSet bs = new BitSet(columns.length);
511+ for (int index = 0; index < updates.length; index++) {
512+ if (updates[index] != NOT_SET) {
513+ bs.set(index);
514+ }
515+ }
516+ Connection conn = Direct.getContext().getConnection();
517+ Map<BitSet, PreparedStatement> map = insertStatementCache.get(conn);
518+ PreparedStatement stmt = null;
519+ if (map == null) {
520+ map = new HashMap<>();
521+ insertStatementCache.put(conn, map);
522+ } else {
523+ stmt = map.get(bs);
524+ }
525+ if (stmt == null) {
526+ StringBuilder updateColumns = new StringBuilder();
527+ StringBuilder updateValues = new StringBuilder();
528+
529+ for (int index = 0; index < columns.length; index++) {
530+ if (updates[index] != NOT_SET) {
531+ if (updateColumns.length() > 0) {
532+ updateColumns.append(',');
533+ updateValues.append(',');
534+ }
535+ updateColumns.append(columns[index].columnName);
536+ updateValues.append('?');
537+ }
538+ }
539+ final String sql = String.format(INSERT_STATEMENT, schemaName, tableName, updateColumns, updateValues);
540+ stmt = conn.prepareStatement(sql);
541+ map.put(bs, stmt);
542+ } else {
543+ // Just in case
544+ stmt.clearParameters();
545+ }
546+ int statementIndex = 1;
547+ for (int index = 0; index < columns.length; index++) {
548+ if (updates[index] != NOT_SET) {
549+ stmt.setObject(statementIndex, updates[index]);
550+ statementIndex++;
551+ }
552+ }
553+ return stmt;
554+ }
555+
556+ private PreparedStatement __updateStatement() throws SQLException {
557+ assert updates != null : "No updates to save";
558+ BitSet bs = new BitSet(columns.length);
559+ for (int index = 0; index < updates.length; index++) {
560+ if (updates[index] != NOT_SET) {
561+ bs.set(index);
562+ }
563+ }
564+ Connection conn = Direct.getContext().getConnection();
565+
566+ Map<BitSet, PreparedStatement> map = updateStatementCache.get(conn);
567+ synchronized (this) {
568+ if (map == null) {
569+ map = new HashMap<>();
570+ updateStatementCache.put(conn, map);
571+ }
572+ }
573+
574+ PreparedStatement stmt = map.get(bs);
575+ if (stmt == null) {
576+ StringBuilder updateColumns = new StringBuilder();
577+ StringBuilder pkColumns = new StringBuilder();
578+
579+ for (int index = 0; index < columns.length; index++) {
580+ if (columns[index].parentJoinFieldIndex >= 0 || columns[index].primaryKeyFieldIndex >= 0) {
581+ if (pkColumns.length() > 0) {
582+ pkColumns.append(" and ");
583+ }
584+ pkColumns.append(columns[index].columnName).append("=?");
585+ }
586+ if (updates[index] != NOT_SET) {
587+ if (updateColumns.length() > 0) {
588+ updateColumns.append(',');
589+ }
590+ updateColumns.append(columns[index].getColumnName()).append("=?");
591+ }
592+ }
593+ final String sql = String.format(UPDATE_STATEMENT, schemaName, tableName, updateColumns, pkColumns);
594+ stmt = conn.prepareStatement(sql);
595+ map.put(bs, stmt);
596+ } else {
597+ // Just in case
598+ stmt.clearParameters();
599+ }
600+ int statementIndex = 1;
601+ for (int pass = 0; pass < 2; pass++) {
602+ for (int index = 0; index < columns.length; index++) {
603+ if (pass == 0) {
604+ if (updates[index] != NOT_SET) {
605+ stmt.setObject(statementIndex, updates[index]);
606+ statementIndex++;
607+ }
608+ }
609+ if (pass == 1) {
610+ if (columns[index].parentJoinFieldIndex >= 0 || columns[index].primaryKeyFieldIndex >= 0) {
611+ stmt.setObject(statementIndex, __getObject(index));
612+ statementIndex++;
613+ }
614+ }
615+ }
616+ }
617+ return stmt;
618 }
619
620 }
621
622=== modified file 'src/main/java/com/akiban/direct/ClassBuilder.java'
623--- src/main/java/com/akiban/direct/ClassBuilder.java 2013-04-05 14:35:40 +0000
624+++ src/main/java/com/akiban/direct/ClassBuilder.java 2013-04-26 02:16:29 +0000
625@@ -16,6 +16,7 @@
626 */
627 package com.akiban.direct;
628
629+import java.util.Collections;
630 import java.util.HashMap;
631 import java.util.Iterator;
632 import java.util.List;
633@@ -53,7 +54,7 @@
634 public abstract void addMethod(String name, String returnType, String[] argumentTypes, String[] argumentNames,
635 String[] body);
636
637- public abstract void addConstructor(String[] argumentTypes, String[] argumentNames, String[] body);
638+ public abstract void addStaticInitializer(final String body);
639
640 /*
641 * (non-Javadoc)
642@@ -89,7 +90,7 @@
643 public static String schemaClassName(String schema) {
644 return PACKAGE + "." + sanitize(INFLECTOR.classify(schema));
645 }
646-
647+
648 public static String sanitize(final String s) {
649 StringBuilder sb = new StringBuilder();
650 for (int i = 0; i < s.length(); i++) {
651@@ -100,7 +101,7 @@
652 }
653 sb.append(ch);
654 } else {
655- sb.append(String.format("_u%04h", ch));
656+ sb.append(String.format("_u%04x", (int) ch));
657 }
658 }
659 return sb.toString();
660@@ -242,7 +243,7 @@
661 String className = PACKAGE + ".$$$" + asJavaName(schemaName, true) + "$$$"
662 + asJavaName(table.getName().getTableName(), true);
663 startClass(className, false, "com.akiban.direct.AbstractDirectObject", new String[] { typeName }, IMPORTS);
664- addConstructor(NONE, NONE, NONE);
665+ addStaticInitializer(columnMetadataString(table));
666 addMethods(table, scn, typeName, className, false);
667 end();
668 }
669@@ -250,6 +251,7 @@
670 void startExtentClass(String schema, final String scn) throws CannotCompileException, NotFoundException {
671 String className = PACKAGE + ".$$" + asJavaName(schema, true);
672 startClass(className, false, "com.akiban.direct.AbstractDirectObject", new String[] { scn }, IMPORTS);
673+ addStaticInitializer(null);
674 }
675
676 void addExtentAccessor(UserTable table, String scn, boolean iface) {
677@@ -258,7 +260,7 @@
678
679 String[] body = null;
680 if (!iface) {
681- StringBuilder sb = new StringBuilder(buildDirectIterableExpr(className, tableName));
682+ StringBuilder sb = new StringBuilder(buildDirectIterableExpr(className, table));
683 body = new String[] { "return " + sb.toString() };
684 }
685 addMethod("get" + asJavaCollectionName(tableName, true), "com.akiban.direct.DirectIterable<" + className + ">",
686@@ -278,7 +280,7 @@
687 names[i] = asJavaName(primaryKeyColumns.get(i).getName(), false);
688 }
689
690- StringBuilder sb = new StringBuilder(buildDirectIterableExpr(className, tableName));
691+ StringBuilder sb = new StringBuilder(buildDirectIterableExpr(className, table));
692 for (int i = 0; i < primaryKeyColumns.size(); i++) {
693 sb.append(String.format(".where(\"%s\", %s)", primaryKeyColumns.get(i).getName(),
694 literal(javaClass(primaryKeyColumns.get(i)), "$" + (i + 1)), false));
695@@ -300,8 +302,10 @@
696 Class<?> javaClass = javaClass(column);
697 String[] getBody = new String[] { "return __get" + column.getType().akType() + "(" + column.getPosition()
698 + ")" };
699- String expr = addProperty(column.getName(), javaClass.getName(), asJavaName(column.getName(), false),
700- iface ? null : getBody, iface ? null : UNSUPPORTED, true);
701+ final String paramName = asJavaName(column.getName(), false);
702+ String[] setBody = new String[] { "__set" + column.getType().akType() + "(" + column.getPosition() + ",$1)" };
703+ String expr = addProperty(column.getName(), javaClass.getName(), paramName, iface ? null : getBody,
704+ iface ? null : setBody, true);
705 getterMethods.put(column.getName(), expr);
706 }
707
708@@ -310,11 +314,12 @@
709 */
710 Join parentJoin = table.getParentJoin();
711 if (parentJoin != null) {
712- String parentTableName = parentJoin.getParent().getName().getTableName();
713+ UserTable parentTable = parentJoin.getParent();
714+ String parentTableName = parentTable.getName().getTableName();
715 String parentClassName = scn + "$" + asJavaName(parentTableName, true);
716 String[] body = null;
717 if (!iface) {
718- StringBuilder sb = new StringBuilder(buildDirectIterableExpr(parentClassName, parentTableName));
719+ StringBuilder sb = new StringBuilder(buildDirectIterableExpr(parentClassName, parentTable));
720 for (final JoinColumn jc : parentJoin.getJoinColumns()) {
721 sb.append(String.format(".where(\"%s\", %s)", jc.getParent().getName(),
722 literal(getterMethods, jc.getParent())));
723@@ -353,7 +358,7 @@
724 }
725 String[] body = null;
726 if (!iface) {
727- StringBuilder sb = new StringBuilder(buildDirectIterableExpr(childClassName, childTableName));
728+ StringBuilder sb = new StringBuilder(buildDirectIterableExpr(childClassName, join.getChild()));
729 for (final JoinColumn jc : join.getJoinColumns()) {
730 sb.append(String.format(".where(\"%s\", %s)", jc.getChild().getName(),
731 literal(getterMethods, jc.getParent())));
732@@ -370,7 +375,7 @@
733 if (!primaryKeyColumns.isEmpty()) {
734 String[] body = null;
735 if (!iface) {
736- StringBuilder sb = new StringBuilder(buildDirectIterableExpr(childClassName, childTableName));
737+ StringBuilder sb = new StringBuilder(buildDirectIterableExpr(childClassName, join.getChild()));
738 for (final JoinColumn jc : join.getJoinColumns()) {
739 sb.append(String.format(".where(\"%s\", %s)", jc.getChild().getName(),
740 literal(getterMethods, jc.getParent())));
741@@ -381,10 +386,56 @@
742 + childClassName + ">", NONE, null, body);
743 }
744 }
745- /*
746- * Add boilerplate methods
747- */
748- addMethod("copy", typeName, NONE, null, iface ? null : UNSUPPORTED);
749+ }
750+
751+ /**
752+ * Generate a Java command that will be executed as a static initializer and
753+ * will give the base class metadata about the columns.
754+ */
755+ @SuppressWarnings("unchecked")
756+ private String columnMetadataString(final UserTable table) {
757+ String[] columnArray = new String[table == null ? 0 : table.getColumns().size()];
758+ if (table != null) {
759+ PrimaryKey pk = table.getPrimaryKey();
760+ List<Column> pkColumns = pk == null ? Collections.EMPTY_LIST : pk.getColumns();
761+ List<JoinColumn> joinColumns = table.getParentJoin() == null ? Collections.EMPTY_LIST : table
762+ .getParentJoin().getJoinColumns();
763+ for (Column column : table.getColumns()) {
764+ int index = column.getPosition();
765+ String columnName = column.getName();
766+ String propertyName = asJavaName(columnName, false);
767+ String type = javaClass(column).getSimpleName();
768+ int pkFieldIndex = -1;
769+ int pjFieldIndex = -1;
770+ for (int pkindex = 0; pkindex < pkColumns.size(); pkindex++) {
771+ if (pkColumns.get(pkindex) == column) {
772+ pkFieldIndex = pkindex;
773+ break;
774+ }
775+ }
776+ for (JoinColumn jc : joinColumns) {
777+ if (jc.getChild() == column) {
778+ pjFieldIndex = jc.getParent().getPosition();
779+ break;
780+ }
781+ }
782+ columnArray[index] = String.format("%s:%s:%s:%d:%d", columnName, propertyName, type, pkFieldIndex,
783+ pjFieldIndex);
784+ }
785+ }
786+ StringBuilder sb = new StringBuilder("__init(");
787+ sb.append("\"").append(table.getName().getSchemaName()).append("\", ");
788+ sb.append("\"").append(table.getName().getTableName()).append("\", ");
789+ sb.append("\"");
790+ for (int index = 0; index < columnArray.length; index++) {
791+ assert columnArray[index] != null : "Missing column specification: " + index;
792+ if (index > 0) {
793+ sb.append(',');
794+ }
795+ sb.append(columnArray[index]);
796+ }
797+ sb.append("\")");
798+ return sb.toString();
799 }
800
801 private Class<?> javaClass(final Column column) {
802@@ -394,7 +445,7 @@
803 case DATE:
804 return java.sql.Date.class;
805 case DATETIME:
806- return java.sql.Date.class;
807+ return java.sql.Timestamp.class;
808 case DECIMAL:
809 return java.math.BigDecimal.class;
810 case DOUBLE:
811@@ -439,8 +490,9 @@
812
813 }
814
815- private String buildDirectIterableExpr(final String className, final String table) {
816- return String.format("(new com.akiban.direct.DirectIterableImpl" + "(%1$s.class, \"%2$s\"))", className, table);
817+ private String buildDirectIterableExpr(final String className, final UserTable table) {
818+ return String.format("(new com.akiban.direct.DirectIterableImpl" + "(%s.class, \"%s\", this))",
819+ className, table.getName().getTableName());
820 }
821
822 /**
823
824=== modified file 'src/main/java/com/akiban/direct/ClassObjectWriter.java'
825--- src/main/java/com/akiban/direct/ClassObjectWriter.java 2013-04-03 13:20:48 +0000
826+++ src/main/java/com/akiban/direct/ClassObjectWriter.java 2013-04-26 02:16:29 +0000
827@@ -87,28 +87,13 @@
828 }
829
830 @Override
831- public void addConstructor(String[] argumentTypes, String[] argumentNames, String[] body) {
832+ public void addStaticInitializer(final String body) {
833 try {
834 if (currentCtClass.isInterface()) {
835 return;
836 }
837- CtClass[] parameters = new CtClass[argumentTypes.length];
838- for (int i = 0; i < argumentTypes.length; i++) {
839- parameters[i] = getCtClass(simpleName(argumentTypes[i]));
840- }
841- CtConstructor method = new CtConstructor(parameters, currentCtClass);
842- if (body != null) {
843- StringBuilder sb = new StringBuilder("{");
844- for (final String s : body) {
845- sb.append(s);
846- sb.append(";");
847- sb.append('\n');
848- }
849- sb.append("}");
850- method.setBody(sb.toString());
851- method.setModifiers(method.getModifiers() & ~Modifier.ABSTRACT);
852- }
853- currentCtClass.addConstructor(method);
854+ CtConstructor initializer = currentCtClass.makeClassInitializer();
855+ initializer.setBody(body + ";");
856 } catch (CannotCompileException e) {
857 e.printStackTrace();
858 }
859
860=== modified file 'src/main/java/com/akiban/direct/ClassSourceWriter.java'
861--- src/main/java/com/akiban/direct/ClassSourceWriter.java 2013-04-03 13:20:48 +0000
862+++ src/main/java/com/akiban/direct/ClassSourceWriter.java 2013-04-26 02:16:29 +0000
863@@ -123,43 +123,17 @@
864 }
865 newLine();
866 }
867-
868+
869 @Override
870- public void addConstructor(final String[] argumentTypes,
871- final String[] argumentNames, final String[] body) {
872- newLine();
873- print("public ", localName(externalName(classNames.firstElement()), classNames.firstElement()), "(");
874- boolean first = true;
875- int counter = 0;
876- for (final String s : argumentTypes) {
877- if (!first) {
878- append(", ");
879- }
880- String argName;
881- if (argumentTypes != null) {
882- argName = argumentNames[counter];
883- counter++;
884- } else {
885- argName = "z" + ++counter;
886- }
887- append(localName(externalName(s), classNames.firstElement()), " ", argName);
888- }
889- append(")");
890- if (body == null) {
891- append(";");
892- } else {
893- append(" {");
894- newLine();
895- indentation++;
896- for (String s : body) {
897- println(s, ";");
898- }
899- indentation--;
900- println("}");
901- }
902+ public void addStaticInitializer(final String body) {
903+ println("static {");
904+ println(body + ";");
905+ println("}");
906 newLine();
907 }
908
909+
910+
911 /*
912 * (non-Javadoc)
913 *
914@@ -188,7 +162,7 @@
915 }
916 boolean shorten = "java.lang.".equals(fqn.substring(0, dotIndex + 1));
917
918- if (!shorten) {
919+ if (!shorten && imports != null) {
920 for (final String s : imports) {
921 if (s.equals(fqn)) {
922 shorten = true;
923
924=== modified file 'src/main/java/com/akiban/direct/ClassXRefWriter.java'
925--- src/main/java/com/akiban/direct/ClassXRefWriter.java 2013-04-03 13:20:48 +0000
926+++ src/main/java/com/akiban/direct/ClassXRefWriter.java 2013-04-26 02:16:29 +0000
927@@ -97,11 +97,6 @@
928 }
929
930 @Override
931- public void addConstructor(final String[] argumentTypes, final String[] argumentNames, final String[] body) {
932- // ignore
933- }
934-
935- @Override
936 public String addProperty(final String name, final String type, final String argName, final String[] getBody,
937 final String[] setBody, final boolean hasSetter) {
938 String caseConverted = asJavaName(name, false);
939@@ -112,6 +107,11 @@
940 addMethod(caseConverted, type, null, null, null);
941 return super.addProperty(name, type, argName, getBody, setBody, hasSetter);
942 }
943+
944+ @Override
945+ public void addStaticInitializer(final String body) {
946+ // ignore
947+ }
948
949 /*
950 * (non-Javadoc)
951
952=== modified file 'src/main/java/com/akiban/direct/Direct.java'
953--- src/main/java/com/akiban/direct/Direct.java 2013-04-03 13:20:48 +0000
954+++ src/main/java/com/akiban/direct/Direct.java 2013-04-26 02:16:29 +0000
955@@ -66,12 +66,7 @@
956 public static AbstractDirectObject objectForRow(final Class<?> c) {
957 AbstractDirectObject o = instanceMap.get().get(c);
958 if (o == null) {
959- try {
960- Class<? extends AbstractDirectObject> cl = classMap.get(c);
961- o = cl.newInstance();
962- } catch (InstantiationException | IllegalAccessException | ClassCastException e) {
963- throw new RuntimeException(e);
964- }
965+ o = newInstance(c);
966 if (o != null) {
967 instanceMap.get().put(c, o);
968 }
969@@ -79,6 +74,21 @@
970 return o;
971 }
972
973+ /**
974+ * Construct a new instance of an entity object of the given type.
975+ * @param c Type (the interface class) of object
976+ * @return A newly constructed implementation object
977+ */
978+ public static AbstractDirectObject newInstance(final Class<?> c) {
979+ try {
980+ Class<? extends AbstractDirectObject> cl = classMap.get(c);
981+ return cl.newInstance();
982+ } catch (InstantiationException | IllegalAccessException | ClassCastException e) {
983+ throw new RuntimeException(e);
984+ }
985+
986+ }
987+
988 public static void enter(final String schemaName, AkibanInformationSchema ais) {
989 DirectClassLoader dcl = ais.getCachedValue(CACHE_KEY, new CacheValueGenerator<DirectClassLoader>() {
990
991
992=== added file 'src/main/java/com/akiban/direct/DirectColumn.java'
993--- src/main/java/com/akiban/direct/DirectColumn.java 1970-01-01 00:00:00 +0000
994+++ src/main/java/com/akiban/direct/DirectColumn.java 2013-04-26 02:16:29 +0000
995@@ -0,0 +1,35 @@
996+/**
997+ * Copyright (C) 2009-2013 Akiban Technologies, Inc.
998+ *
999+ * This program is free software: you can redistribute it and/or modify
1000+ * it under the terms of the GNU Affero General Public License as published by
1001+ * the Free Software Foundation, either version 3 of the License, or
1002+ * (at your option) any later version.
1003+ *
1004+ * This program is distributed in the hope that it will be useful,
1005+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1006+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1007+ * GNU Affero General Public License for more details.
1008+ *
1009+ * You should have received a copy of the GNU Affero General Public License
1010+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1011+ */
1012+
1013+package com.akiban.direct;
1014+
1015+
1016+/**
1017+ * Metadata supplied by DirectObject
1018+ *
1019+ * @author peter
1020+ *
1021+ */
1022+public interface DirectColumn {
1023+
1024+ int getColumnIndex();
1025+ String getColumnName();
1026+ String getPropertyName();
1027+ String getPropertyType();
1028+
1029+
1030+}
1031
1032=== modified file 'src/main/java/com/akiban/direct/DirectIterable.java'
1033--- src/main/java/com/akiban/direct/DirectIterable.java 2013-03-22 20:05:57 +0000
1034+++ src/main/java/com/akiban/direct/DirectIterable.java 2013-04-26 02:16:29 +0000
1035@@ -18,7 +18,7 @@
1036 package com.akiban.direct;
1037
1038 /**
1039- * A List expanded to accept selection, limit and sort capabilities.
1040+ * An Iterable expanded to accept selection, limit and sort capabilities.
1041 *
1042 * @author peter
1043 *
1044@@ -35,5 +35,6 @@
1045
1046 public DirectIterable<T> limit(String limit);
1047
1048+ public T newInstance();
1049
1050 }
1051
1052=== modified file 'src/main/java/com/akiban/direct/DirectIterableImpl.java'
1053--- src/main/java/com/akiban/direct/DirectIterableImpl.java 2013-03-22 20:05:57 +0000
1054+++ src/main/java/com/akiban/direct/DirectIterableImpl.java 2013-04-26 02:16:29 +0000
1055@@ -23,9 +23,7 @@
1056 import java.util.Iterator;
1057 import java.util.List;
1058
1059-import com.akiban.qp.row.Row;
1060 import com.akiban.sql.embedded.JDBCResultSet;
1061-import com.akiban.util.ShareHolder;
1062
1063 /**
1064 * Very kludgey implementation by constructing SQL strings.
1065@@ -36,22 +34,24 @@
1066 */
1067 public class DirectIterableImpl<T> implements DirectIterable<T> {
1068
1069- final Class<T> clazz;
1070- boolean hasNext;
1071-
1072- final String table;
1073- final List<String> predicates = new ArrayList<String>();
1074- final List<String> sorts = new ArrayList<String>();
1075- String limit;
1076-
1077- boolean initialized;
1078- String sql;
1079-
1080- JDBCResultSet resultSet;
1081-
1082- public DirectIterableImpl(Class<T> clazz, String toTable) {
1083- this.clazz = clazz;
1084+ private final Class<T> clazz;
1085+ private final DirectObject parent;
1086+ private boolean hasNext;
1087+
1088+ private final String table;
1089+ private final List<String> predicates = new ArrayList<String>();
1090+ private final List<String> sorts = new ArrayList<String>();
1091+ private String limit;
1092+
1093+ private boolean initialized;
1094+ private String sql;
1095+
1096+ private JDBCResultSet resultSet;
1097+
1098+ public DirectIterableImpl(Class<T> ifaceClass, String toTable, DirectObject parent) {
1099+ this.clazz = ifaceClass;
1100 this.table = toTable;
1101+ this.parent = parent;
1102 }
1103
1104 @Override
1105@@ -104,7 +104,6 @@
1106
1107 private boolean nextRow() {
1108 try {
1109-
1110 boolean result = resultSet.next();
1111 return result;
1112 } catch (SQLException e) {
1113@@ -152,7 +151,7 @@
1114 predicates.add(predicate);
1115 return this;
1116 }
1117-
1118+
1119 @Override
1120 public DirectIterableImpl<T> where(final String columnName, Object literal) {
1121 StringBuilder sb = new StringBuilder(columnName).append(" = ");
1122@@ -164,7 +163,6 @@
1123 predicates.add(sb.toString());
1124 return this;
1125 }
1126-
1127
1128 @Override
1129 public DirectIterableImpl<T> sort(final String column) {
1130@@ -186,4 +184,16 @@
1131 }
1132 throw new IllegalStateException("Limit already specified");
1133 }
1134+
1135+ @SuppressWarnings("unchecked")
1136+ @Override
1137+ public T newInstance() throws DirectException {
1138+ try {
1139+ final AbstractDirectObject newInstance = Direct.newInstance(clazz);
1140+ newInstance.populateJoinFields(parent);
1141+ return (T)newInstance;
1142+ } catch (Exception e) {
1143+ throw new DirectException(e);
1144+ }
1145+ }
1146 }
1147
1148=== modified file 'src/main/java/com/akiban/rest/RestResponseBuilder.java'
1149--- src/main/java/com/akiban/rest/RestResponseBuilder.java 2013-04-20 11:38:26 +0000
1150+++ src/main/java/com/akiban/rest/RestResponseBuilder.java 2013-04-26 02:16:29 +0000
1151@@ -19,6 +19,8 @@
1152
1153 import com.akiban.rest.resources.ResourceHelper;
1154 import com.akiban.server.Quote;
1155+import com.akiban.server.error.DirectEndpointNotFoundException;
1156+import com.akiban.server.error.DirectTransactionFailedException;
1157 import com.akiban.server.error.ErrorCode;
1158 import com.akiban.server.error.InvalidOperationException;
1159 import com.akiban.server.error.NoSuchRoutineException;
1160@@ -145,9 +147,8 @@
1161
1162 public WebApplicationException wrapException(Throwable e) {
1163 final ErrorCode code;
1164- if (e instanceof WebApplicationException) {
1165- return (WebApplicationException)e;
1166- } else if(e instanceof InvalidOperationException) {
1167+
1168+ if(e instanceof InvalidOperationException) {
1169 code = ((InvalidOperationException)e).getCode();
1170 } else if(e instanceof SQLException) {
1171 code = ErrorCode.valueOfCode(((SQLException)e).getSQLState());
1172@@ -204,6 +205,8 @@
1173 Map<Class, Response.Status> map = new HashMap<>();
1174 map.put(NoSuchTableException.class, Response.Status.NOT_FOUND);
1175 map.put(NoSuchRoutineException.class, Response.Status.NOT_FOUND);
1176+ map.put(DirectEndpointNotFoundException.class, Response.Status.NOT_FOUND);
1177+ map.put(DirectTransactionFailedException.class, Response.Status.INTERNAL_SERVER_ERROR);
1178 map.put(JsonParseException.class, Response.Status.BAD_REQUEST);
1179 return map;
1180 }
1181
1182=== modified file 'src/main/java/com/akiban/rest/resources/DirectResource.java'
1183--- src/main/java/com/akiban/rest/resources/DirectResource.java 2013-04-20 11:44:31 +0000
1184+++ src/main/java/com/akiban/rest/resources/DirectResource.java 2013-04-26 02:16:29 +0000
1185@@ -18,14 +18,8 @@
1186 package com.akiban.rest.resources;
1187
1188 import static com.akiban.rest.resources.ResourceHelper.JSONP_ARG_NAME;
1189-import static com.akiban.rest.resources.ResourceHelper.checkSchemaAccessible;
1190-import static com.akiban.rest.resources.ResourceHelper.checkTableAccessible;
1191-import static com.akiban.util.JsonUtils.createJsonGenerator;
1192
1193-import java.io.IOException;
1194 import java.io.PrintWriter;
1195-import java.util.List;
1196-import java.util.Map;
1197
1198 import javax.servlet.http.HttpServletRequest;
1199 import javax.ws.rs.DELETE;
1200@@ -66,8 +60,6 @@
1201 private final static String LANGUAGE = "language";
1202 private final static String FUNCTIONS = "functions";
1203
1204-
1205-
1206 private final ResourceRequirements reqs;
1207
1208 public DirectResource(ResourceRequirements reqs) {
1209
1210=== added file 'src/main/java/com/akiban/server/error/DirectEndpointNotFoundException.java'
1211--- src/main/java/com/akiban/server/error/DirectEndpointNotFoundException.java 1970-01-01 00:00:00 +0000
1212+++ src/main/java/com/akiban/server/error/DirectEndpointNotFoundException.java 2013-04-26 02:16:29 +0000
1213@@ -0,0 +1,25 @@
1214+/**
1215+ * Copyright (C) 2009-2013 Akiban Technologies, Inc.
1216+ *
1217+ * This program is free software: you can redistribute it and/or modify
1218+ * it under the terms of the GNU Affero General Public License as published by
1219+ * the Free Software Foundation, either version 3 of the License, or
1220+ * (at your option) any later version.
1221+ *
1222+ * This program is distributed in the hope that it will be useful,
1223+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1224+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1225+ * GNU Affero General Public License for more details.
1226+ *
1227+ * You should have received a copy of the GNU Affero General Public License
1228+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1229+ */
1230+
1231+package com.akiban.server.error;
1232+
1233+
1234+public class DirectEndpointNotFoundException extends InvalidOperationException {
1235+ public DirectEndpointNotFoundException(Object... args) {
1236+ super(ErrorCode.DIRECT_ENDPOINT_NOT_FOUND, args);
1237+ }
1238+}
1239
1240=== added file 'src/main/java/com/akiban/server/error/DirectTransactionFailedException.java'
1241--- src/main/java/com/akiban/server/error/DirectTransactionFailedException.java 1970-01-01 00:00:00 +0000
1242+++ src/main/java/com/akiban/server/error/DirectTransactionFailedException.java 2013-04-26 02:16:29 +0000
1243@@ -0,0 +1,25 @@
1244+/**
1245+ * Copyright (C) 2009-2013 Akiban Technologies, Inc.
1246+ *
1247+ * This program is free software: you can redistribute it and/or modify
1248+ * it under the terms of the GNU Affero General Public License as published by
1249+ * the Free Software Foundation, either version 3 of the License, or
1250+ * (at your option) any later version.
1251+ *
1252+ * This program is distributed in the hope that it will be useful,
1253+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1254+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1255+ * GNU Affero General Public License for more details.
1256+ *
1257+ * You should have received a copy of the GNU Affero General Public License
1258+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1259+ */
1260+
1261+package com.akiban.server.error;
1262+
1263+
1264+public class DirectTransactionFailedException extends InvalidOperationException {
1265+ public DirectTransactionFailedException(Object... args) {
1266+ super(ErrorCode.DIRECT_TRANSACTION_FAILED, args);
1267+ }
1268+}
1269
1270=== modified file 'src/main/java/com/akiban/server/error/ErrorCode.java'
1271--- src/main/java/com/akiban/server/error/ErrorCode.java 2013-04-14 03:16:25 +0000
1272+++ src/main/java/com/akiban/server/error/ErrorCode.java 2013-04-26 02:16:29 +0000
1273@@ -284,6 +284,8 @@
1274 // Class 42/800 - Akiba Direct errors
1275 CANT_CALL_SCRIPT_LIBRARY ("42", "800", Importance.DEBUG, CantCallScriptLibraryException.class),
1276 SCRIPT_REGISTRATION_EXCEPTION ("42", "801", Importance.DEBUG, ScriptLibraryRegistrationException.class),
1277+ DIRECT_ENDPOINT_NOT_FOUND ("42", "802", Importance.DEBUG, DirectEndpointNotFoundException.class),
1278+ DIRECT_TRANSACTION_FAILED ("42", "803", Importance.ERROR, DirectTransactionFailedException.class),
1279
1280 // Class 44 - with check option violation
1281
1282
1283=== modified file 'src/main/java/com/akiban/server/service/restdml/DirectServiceImpl.java'
1284--- src/main/java/com/akiban/server/service/restdml/DirectServiceImpl.java 2013-04-23 01:07:03 +0000
1285+++ src/main/java/com/akiban/server/service/restdml/DirectServiceImpl.java 2013-04-26 02:16:29 +0000
1286@@ -52,6 +52,8 @@
1287 import com.akiban.rest.RestFunctionRegistrar;
1288 import com.akiban.rest.RestServiceImpl;
1289 import com.akiban.rest.resources.ResourceHelper;
1290+import com.akiban.server.error.DirectEndpointNotFoundException;
1291+import com.akiban.server.error.DirectTransactionFailedException;
1292 import com.akiban.server.error.ExternalRoutineInvocationException;
1293 import com.akiban.server.error.NoSuchRoutineException;
1294 import com.akiban.server.error.ScriptLibraryRegistrationException;
1295@@ -94,6 +96,7 @@
1296 private final static String IS_RESULT = "is_result";
1297
1298 private final static int TRANSACTION_RETRY_COUNT = 3;
1299+
1300 private final static String DISTINGUISHED_REGISTRATION_METHOD_NAME = "_register";
1301
1302 private final static String CREATE_PROCEDURE_FORMAT = "CREATE OR REPLACE PROCEDURE \"%s\".\"%s\" ()"
1303@@ -336,7 +339,7 @@
1304 final TableName procName, final String pathParams, final MultivaluedMap<String, String> queryParameters,
1305 final byte[] content, final MediaType[] responseType) throws Exception {
1306 try (JDBCConnection conn = jdbcConnection(request, procName.getSchemaName());) {
1307- LOG.debug("Invoking {} {}", request.getMethod(), request.getRequestURI());
1308+ LOG.debug("Invoking {} {}", method, request.getRequestURI());
1309 conn.setAutoCommit(false);
1310
1311 boolean completed = false;
1312@@ -348,7 +351,7 @@
1313 Direct.getContext().setConnection(conn);
1314 conn.beginTransaction();
1315 invokeRestFunction(writer, conn, method, procName, pathParams, queryParameters, content,
1316- request.getContentType(), responseType);
1317+ request, responseType);
1318 conn.commitTransaction();
1319 completed = true;
1320 return;
1321@@ -356,7 +359,7 @@
1322 if (repeat == 0) {
1323 LOG.error("Transaction failed " + TRANSACTION_RETRY_COUNT + " times: "
1324 + request.getRequestURI());
1325- throw new WebApplicationException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1326+ throw new DirectTransactionFailedException(method, request.getRequestURI());
1327 }
1328 } catch (RegistrationException e) {
1329 throw new ScriptLibraryRegistrationException(e);
1330@@ -383,7 +386,7 @@
1331
1332 private void invokeRestFunction(final PrintWriter writer, JDBCConnection conn, final String method,
1333 final TableName procName, final String pathParams, final MultivaluedMap<String, String> queryParameters,
1334- final byte[] content, final String requestType, final MediaType[] responseType) throws Exception {
1335+ final byte[] content, final HttpServletRequest request, final MediaType[] responseType) throws Exception {
1336
1337 ParamCache cache = new ParamCache();
1338 final EndpointMap endpointMap = getEndpointMap(conn.getSession());
1339@@ -392,9 +395,9 @@
1340 list = endpointMap.getMap().get(new EndpointAddress(method, procName));
1341 }
1342
1343- EndpointMetadata md = selectEndpoint(list, pathParams, requestType, responseType, cache);
1344+ EndpointMetadata md = selectEndpoint(list, pathParams, request.getContentType(), responseType, cache);
1345 if (md == null) {
1346- throw new WebApplicationException(HttpServletResponse.SC_NOT_FOUND);
1347+ throw new DirectEndpointNotFoundException(method, request.getRequestURI());
1348 }
1349
1350 final Object[] args = createArgsArray(pathParams, queryParameters, content, cache, md);
1351@@ -603,7 +606,7 @@
1352 list.add(em);
1353 }
1354 } catch (Exception e) {
1355- String msg = e instanceof IllegalArgumentException ? e.getMessage() : "";
1356+ String msg = IllegalArgumentException.class.equals(e.getClass()) ? e.getMessage() : e.toString();
1357 throw new RegistrationException("Invalid function specification: " + spec + " - " + msg, e);
1358 }
1359 }
1360
1361=== modified file 'src/main/java/com/akiban/server/service/restdml/EndpointMetadata.java'
1362--- src/main/java/com/akiban/server/service/restdml/EndpointMetadata.java 2013-04-21 01:28:30 +0000
1363+++ src/main/java/com/akiban/server/service/restdml/EndpointMetadata.java 2013-04-26 02:16:29 +0000
1364@@ -227,7 +227,7 @@
1365 em.name = v;
1366 } else {
1367 em.name = v.substring(0, p);
1368- em.pattern = Pattern.compile(v.substring(p));
1369+ em.pattern = Pattern.compile(v.substring(p + 1));
1370 }
1371 break;
1372 }
1373@@ -576,7 +576,7 @@
1374 @Override
1375 public boolean equals(Object other) {
1376 EndpointAddress ea = (EndpointAddress) other;
1377- return name.equals(ea.name) && method.equals(ea.method);
1378+ return name.equals(ea.name) && method.equals(ea.method) && schema.equals(ea.schema);
1379 }
1380
1381 }
1382@@ -774,7 +774,7 @@
1383 StringBuilder sb = new StringBuilder();
1384 append(sb, METHOD, "=", method, " ", PATH, "=", name);
1385 if (pattern != null) {
1386- append(sb, pattern.toString());
1387+ append(sb, "/", pattern.toString());
1388 }
1389 append(sb, " ", FUNCTION, "=", function, " ", IN, "=(");
1390 if (inParams == null) {
1391@@ -802,7 +802,7 @@
1392
1393 Matcher getParamPathMatcher(final ParamCache cache, final String pathParamString) {
1394 if (cache.matcher == null) {
1395- cache.matcher = pattern.matcher(pathParamString);
1396+ cache.matcher = pattern.matcher(pathParamString.isEmpty() ? pathParamString : pathParamString.substring(1));
1397 }
1398 return cache.matcher;
1399 }
1400
1401=== modified file 'src/main/java/com/akiban/server/types3/TClassBase.java'
1402--- src/main/java/com/akiban/server/types3/TClassBase.java 2013-03-22 20:05:57 +0000
1403+++ src/main/java/com/akiban/server/types3/TClassBase.java 2013-04-26 02:16:29 +0000
1404@@ -121,6 +121,8 @@
1405 PValueTargets.copyFrom(in, out);
1406 return true;
1407 }
1408+
1409+
1410 PUnderlying underlyingType = TInstance.pUnderlying(in.tInstance());
1411 if (underlyingType == PUnderlying.STRING || underlyingType == PUnderlying.BYTES)
1412 return false;
1413
1414=== modified file 'src/main/java/com/akiban/sql/embedded/JDBCResultSet.java'
1415--- src/main/java/com/akiban/sql/embedded/JDBCResultSet.java 2013-03-22 20:05:57 +0000
1416+++ src/main/java/com/akiban/sql/embedded/JDBCResultSet.java 2013-04-26 02:16:29 +0000
1417@@ -57,7 +57,7 @@
1418 context = new EmbeddedQueryContext(this);
1419 values = new Values();
1420 }
1421-
1422+
1423 protected class Values extends ServerJavaValues {
1424 @Override
1425 protected int size() {
1426@@ -1517,7 +1517,7 @@
1427
1428 AbstractDirectObject o = Direct.objectForRow(c);
1429 if (o != null) {
1430- o.setResults(values, this);
1431+ o.setResults(this);
1432 return o;
1433 }
1434 throw new JDBCException("No entity class for row");
1435@@ -1526,4 +1526,5 @@
1436 public boolean hasRow() {
1437 return row != null;
1438 }
1439+
1440 }
1441
1442=== modified file 'src/main/resources/com/akiban/server/error/error_code.properties'
1443--- src/main/resources/com/akiban/server/error/error_code.properties 2013-04-14 03:16:25 +0000
1444+++ src/main/resources/com/akiban/server/error/error_code.properties 2013-04-26 02:16:29 +0000
1445@@ -160,6 +160,8 @@
1446 #
1447 CANT_CALL_SCRIPT_LIBRARY = Cannot call a script library directly: {0}
1448 SCRIPT_REGISTRATION_EXCEPTION = Script library registration error: {0}
1449+DIRECT_ENDPOINT_NOT_FOUND = Endpoint not found {0} {1}
1450+DIRECT_TRANSACTION_FAILED = Transaction failed in {0} {1}
1451 #
1452 # Class 46 - SQL/J
1453 #
1454
1455=== added directory 'src/test/java/com/akiban/direct'
1456=== added file 'src/test/java/com/akiban/direct/COIDirectClasses.java'
1457--- src/test/java/com/akiban/direct/COIDirectClasses.java 1970-01-01 00:00:00 +0000
1458+++ src/test/java/com/akiban/direct/COIDirectClasses.java 2013-04-26 02:16:29 +0000
1459@@ -0,0 +1,288 @@
1460+/**
1461+ * Copyright (C) 2009-2013 Akiban Technologies, Inc.
1462+ *
1463+ * This program is free software: you can redistribute it and/or modify
1464+ * it under the terms of the GNU Affero General Public License as published by
1465+ * the Free Software Foundation, either version 3 of the License, or
1466+ * (at your option) any later version.
1467+ *
1468+ * This program is distributed in the hope that it will be useful,
1469+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1470+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1471+ * GNU Affero General Public License for more details.
1472+ *
1473+ * You should have received a copy of the GNU Affero General Public License
1474+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1475+ */
1476+
1477+package com.akiban.direct;
1478+
1479+public class COIDirectClasses {
1480+
1481+ //
1482+ // Interfaces and classes are adapted from source code generated by {@link
1483+ // com.akiban.direct.ClassBuilder}.
1484+ // ------------------------------------------------------------------------------------------
1485+ //
1486+ interface Iface extends DirectObject {
1487+ interface Address {
1488+ public int getAid();
1489+
1490+ public void setAid(int aid);
1491+
1492+ public int getCid();
1493+
1494+ public void setCid(int cid);
1495+
1496+ public String getState();
1497+
1498+ public void setState(String state);
1499+
1500+ public String getCity();
1501+
1502+ public void setCity(String city);
1503+
1504+ public Customer getCustomer();
1505+
1506+ public void save();
1507+ }
1508+
1509+ interface Customer extends DirectObject {
1510+ public int getCid();
1511+
1512+ public void setCid(int cid);
1513+
1514+ public String getName();
1515+
1516+ public void setName(String name);
1517+
1518+ public Address getAddress(int aid);
1519+
1520+ public DirectIterable<Address> getAddresses();
1521+
1522+ public Order getOrder(int oid);
1523+
1524+ public DirectIterable<Order> getOrders();
1525+
1526+ public void save();
1527+ }
1528+
1529+ interface Item extends DirectObject {
1530+ public int getIid();
1531+
1532+ public void setIid(int iid);
1533+
1534+ public int getOid();
1535+
1536+ public void setOid(int oid);
1537+
1538+ public String getSku();
1539+
1540+ public void setSku(String sku);
1541+
1542+ public Order getOrder();
1543+
1544+ public void save();
1545+ }
1546+
1547+ interface Order extends DirectObject {
1548+ public int getOid();
1549+
1550+ public void setOid(int oid);
1551+
1552+ public int getCid();
1553+
1554+ public void setCid(int cid);
1555+
1556+ public java.sql.Date getOdate();
1557+
1558+ public void setOdate(java.sql.Date odate);
1559+
1560+ public Customer getCustomer();
1561+
1562+ public Item getItem(int iid);
1563+
1564+ public DirectIterable<Item> getItems();
1565+
1566+ public void save();
1567+ }
1568+ }
1569+
1570+ static class Test$Customer extends com.akiban.direct.AbstractDirectObject implements Iface.Customer {
1571+ static {
1572+ __init("test", "customers", "cid:cid:int:0:-1,name:name:String:-1:-1");
1573+ }
1574+
1575+ public int getCid() {
1576+ return __getINT(0);
1577+ }
1578+
1579+ public void setCid(int cid) {
1580+ __setINT(0, cid);
1581+ }
1582+
1583+ public String getName() {
1584+ return __getVARCHAR(1);
1585+ }
1586+
1587+ public void setName(String name) {
1588+ __setVARCHAR(1, name);
1589+ }
1590+
1591+ public Iface.Address getAddress(int aid) {
1592+ return (new com.akiban.direct.DirectIterableImpl<Iface.Address>(Iface.Address.class, "addresses", this))
1593+ .where("cid", Integer.valueOf(getCid())).where("aid", Integer.valueOf(aid)).single();
1594+ }
1595+
1596+ public com.akiban.direct.DirectIterable<Iface.Address> getAddresses() {
1597+ return (new com.akiban.direct.DirectIterableImpl<Iface.Address>(Iface.Address.class, "addresses", this)).where("cid",
1598+ Integer.valueOf(getCid()));
1599+ }
1600+
1601+ public Iface.Order getOrder(int oid) {
1602+ return (new com.akiban.direct.DirectIterableImpl<Iface.Order>(Iface.Order.class, "orders", this))
1603+ .where("cid", Integer.valueOf(getCid())).where("oid", Integer.valueOf(oid)).single();
1604+ }
1605+
1606+ public com.akiban.direct.DirectIterable<Iface.Order> getOrders() {
1607+ return (new com.akiban.direct.DirectIterableImpl<Iface.Order>(Iface.Order.class, "orders", this)).where("cid",
1608+ Integer.valueOf(getCid()));
1609+ }
1610+
1611+ }
1612+
1613+ static class Test$Order extends com.akiban.direct.AbstractDirectObject implements Iface.Order {
1614+ static {
1615+ __init("test", "orders", "oid:oid:int:0:-1,cid:cid:int:-1:0,odate:odate:Date:-1:-1");
1616+ }
1617+
1618+ public int getOid() {
1619+ return __getINT(0);
1620+ }
1621+
1622+ public void setOid(int oid) {
1623+ __setINT(0, oid);
1624+ }
1625+
1626+ public int getCid() {
1627+ return __getINT(1);
1628+ }
1629+
1630+ public void setCid(int cid) {
1631+ __setINT(1, cid);
1632+ }
1633+
1634+ public java.sql.Date getOdate() {
1635+ return __getDATE(2);
1636+ }
1637+
1638+ public void setOdate(java.sql.Date odate) {
1639+ __setDATE(2, odate);
1640+ }
1641+
1642+ public Iface.Item getItem(int iid) {
1643+ return (new com.akiban.direct.DirectIterableImpl<Iface.Item>(Iface.Item.class, "items", this))
1644+ .where("oid", Integer.valueOf(getOid())).where("iid", Integer.valueOf(iid)).single();
1645+ }
1646+
1647+ public com.akiban.direct.DirectIterable<Iface.Item> getItems() {
1648+ return (new com.akiban.direct.DirectIterableImpl<Iface.Item>(Iface.Item.class, "items", this)).where("oid",
1649+ Integer.valueOf(getOid()));
1650+ }
1651+
1652+ public Iface.Customer getCustomer() {
1653+ return (new com.akiban.direct.DirectIterableImpl<Iface.Customer>(Iface.Customer.class, "customers", this))
1654+ .where("cid", Integer.valueOf(getCid())).single();
1655+ }
1656+
1657+ }
1658+
1659+ static class Test$Item extends com.akiban.direct.AbstractDirectObject implements Iface.Item {
1660+ static {
1661+ __init("test", "items", "iid:iid:int:0:-1,oid:oid:int:-1:0,sku:sku:String:-1:-1");
1662+ }
1663+
1664+ public int getIid() {
1665+ return __getINT(0);
1666+ }
1667+
1668+ public void setIid(int iid) {
1669+ __setINT(0, iid);
1670+ }
1671+
1672+ public int getOid() {
1673+ return __getINT(1);
1674+ }
1675+
1676+ public void setOid(int oid) {
1677+ __setINT(1, oid);
1678+ }
1679+
1680+ public String getSku() {
1681+ return __getVARCHAR(2);
1682+ }
1683+
1684+ public void setSku(String sku) {
1685+ __setVARCHAR(2, sku);
1686+ }
1687+
1688+ public Iface.Order getOrder() {
1689+ return (new com.akiban.direct.DirectIterableImpl<Iface.Order>(Iface.Order.class, "orders", this)).where(
1690+ "oid", Integer.valueOf(getOid())).single();
1691+ }
1692+
1693+ }
1694+
1695+ static class Test$Address extends com.akiban.direct.AbstractDirectObject implements Iface.Address {
1696+ static {
1697+ __init("test", "addresses",
1698+ "aid:aid:int:0:-1,cid:cid:int:-1:0,state:state:String:-1:-1,city:city:String:-1:-1");
1699+ }
1700+
1701+ public int getAid() {
1702+ return __getINT(0);
1703+ }
1704+
1705+ public void setAid(int aid) {
1706+ __setINT(0, aid);
1707+ }
1708+
1709+ public int getCid() {
1710+ return __getINT(1);
1711+ }
1712+
1713+ public void setCid(int cid) {
1714+ __setINT(1, cid);
1715+ }
1716+
1717+ public String getState() {
1718+ return __getVARCHAR(2);
1719+ }
1720+
1721+ public void setState(String state) {
1722+ __setVARCHAR(2, state);
1723+ }
1724+
1725+ public String getCity() {
1726+ return __getVARCHAR(3);
1727+ }
1728+
1729+ public void setCity(String city) {
1730+ __setVARCHAR(3, city);
1731+ }
1732+
1733+ public Iface.Customer getCustomer() {
1734+ return (new com.akiban.direct.DirectIterableImpl<Iface.Customer>(Iface.Customer.class, "customers", this))
1735+ .where("cid", Integer.valueOf(getCid())).single();
1736+ }
1737+
1738+ }
1739+
1740+ static void registerDirect() {
1741+ Direct.registerDirectObjectClass(Iface.Address.class, Test$Address.class);
1742+ Direct.registerDirectObjectClass(Iface.Customer.class, Test$Customer.class);
1743+ Direct.registerDirectObjectClass(Iface.Order.class, Test$Order.class);
1744+ Direct.registerDirectObjectClass(Iface.Item.class, Test$Item.class);
1745+ }
1746+
1747+}
1748
1749=== added file 'src/test/java/com/akiban/direct/DirectUpdateIT.java'
1750--- src/test/java/com/akiban/direct/DirectUpdateIT.java 1970-01-01 00:00:00 +0000
1751+++ src/test/java/com/akiban/direct/DirectUpdateIT.java 2013-04-26 02:16:29 +0000
1752@@ -0,0 +1,215 @@
1753+/**
1754+ * Copyright (C) 2009-2013 Akiban Technologies, Inc.
1755+ *
1756+ * This program is free software: you can redistribute it and/or modify
1757+ * it under the terms of the GNU Affero General Public License as published by
1758+ * the Free Software Foundation, either version 3 of the License, or
1759+ * (at your option) any later version.
1760+ *
1761+ * This program is distributed in the hope that it will be useful,
1762+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1763+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1764+ * GNU Affero General Public License for more details.
1765+ *
1766+ * You should have received a copy of the GNU Affero General Public License
1767+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1768+ */
1769+
1770+package com.akiban.direct;
1771+
1772+import static org.junit.Assert.assertEquals;
1773+import static org.junit.Assert.assertNotNull;
1774+import static org.junit.Assert.assertTrue;
1775+import static org.junit.Assert.fail;
1776+
1777+import java.io.File;
1778+import java.sql.Date;
1779+import java.sql.SQLException;
1780+import java.sql.Timestamp;
1781+
1782+import org.junit.After;
1783+import org.junit.Before;
1784+import org.junit.Test;
1785+import org.slf4j.Logger;
1786+import org.slf4j.LoggerFactory;
1787+
1788+import com.akiban.direct.COIDirectClasses.Iface.Customer;
1789+import com.akiban.direct.COIDirectClasses.Iface.Order;
1790+import com.akiban.server.service.servicemanager.GuicedServiceManager;
1791+import com.akiban.server.test.it.ITBase;
1792+import com.akiban.sql.RegexFilenameFilter;
1793+import com.akiban.sql.embedded.EmbeddedJDBCService;
1794+import com.akiban.sql.embedded.EmbeddedJDBCServiceImpl;
1795+import com.akiban.sql.embedded.JDBCConnection;
1796+
1797+public final class DirectUpdateIT extends ITBase {
1798+
1799+ private static final Logger LOG = LoggerFactory.getLogger(DirectUpdateIT.class.getName());
1800+
1801+ private static final File RESOURCE_DIR = new File("src/test/resources/"
1802+ + DirectUpdateIT.class.getPackage().getName().replace('.', '/'));
1803+
1804+ public static final String SCHEMA_NAME = "test";
1805+
1806+ @Override
1807+ protected GuicedServiceManager.BindingsConfigurationProvider serviceBindingsProvider() {
1808+ // JDBC service is not in test-services.
1809+ return super.serviceBindingsProvider().bindAndRequire(EmbeddedJDBCService.class, EmbeddedJDBCServiceImpl.class);
1810+ }
1811+
1812+ @Before
1813+ public void setUp() throws Exception {
1814+ startTestServices();
1815+ loadDatabase();
1816+ Direct.enter("test", ais());
1817+ COIDirectClasses.registerDirect();
1818+ }
1819+
1820+ @After
1821+ public void tearDown() throws Exception {
1822+ Direct.unregisterDirectObjectClasses();
1823+ Direct.leave();
1824+ super.tearDownAllTables();
1825+ }
1826+
1827+ @Test
1828+ public void testGetCustomerAndInterate() throws Exception {
1829+ test(new TestExec() {
1830+ public boolean exec() throws Exception {
1831+ final Customer customer = new DirectIterableImpl<Customer>(
1832+ Customer.class, "customers", this).where("cid=1").single();
1833+ assertEquals("Customer has cid", 1, customer.getCid());
1834+ int orderCount = 0;
1835+ for (Order order : customer.getOrders()) {
1836+ orderCount++;
1837+ assertEquals("Customer's Order's Customer is correct", customer, order.getCustomer());
1838+ }
1839+ assertEquals("Customer 1 has 2 orders", 2, orderCount);
1840+ return true;
1841+ }
1842+ });
1843+ }
1844+
1845+ @Test
1846+ public void insertNewOrderExpectSuccess() throws Exception {
1847+ test(new TestExec() {
1848+ public boolean exec() throws Exception {
1849+ final Customer customer = new DirectIterableImpl<Customer>(
1850+ Customer.class, "customers", this).where("cid=1").single();
1851+ final Order newOrder = customer.getOrders().newInstance();
1852+
1853+ newOrder.setOid(103);
1854+ newOrder.setOdate(java.sql.Date.valueOf("2011-03-03"));
1855+ newOrder.save();
1856+
1857+ int orderCount = 0;
1858+ for (Order o : customer.getOrders()) {
1859+ orderCount++;
1860+ assertEquals("Customer's Order's Customer is correct", customer, o.getCustomer());
1861+ }
1862+ assertEquals("Customer 1 now has 3 orders", 3, orderCount);
1863+ return true;
1864+ }
1865+ });
1866+ }
1867+
1868+ @Test
1869+ public void updateOrderOnce() throws Exception {
1870+ test(new TestExec() {
1871+ public boolean exec() throws Exception {
1872+ final Customer customer = new DirectIterableImpl<Customer>(
1873+ Customer.class, "customers", this).where("cid=1").single();
1874+ final Order order = customer.getOrder(101);
1875+ assertNotNull("Customer 1 has an order with oid=101", order);
1876+
1877+ order.setOid(103);
1878+ order.setOdate(java.sql.Date.valueOf("2011-03-03"));
1879+ order.save();
1880+
1881+ int orderCount = 0;
1882+ for (Order o : customer.getOrders()) {
1883+ orderCount++;
1884+ assertEquals("Customer's Order's Customer is correct", customer, o.getCustomer());
1885+ }
1886+ assertEquals("Customer 1 now has 2 orders", 2, orderCount);
1887+ return true;
1888+ }
1889+ });
1890+ }
1891+
1892+ @Test
1893+ public void updateMultipleOrders() throws Exception {
1894+ test(new TestExec() {
1895+ public boolean exec() throws Exception {
1896+ final Customer customer = new DirectIterableImpl<Customer>(
1897+ Customer.class, "customers", this).where("cid=1").single();
1898+ for (final Order order : customer.getOrders()) {
1899+ Date newDate = Date.valueOf("2013-01-" + order.getOdate().toString().substring(8));
1900+ order.setOdate(newDate);
1901+ order.save();
1902+ }
1903+ int orderCount = 0;
1904+ Timestamp after = Timestamp.valueOf("2012-12-31 23:59:59");
1905+ for (Order o : customer.getOrders()) {
1906+ if (o.getOdate().after(after)) {
1907+ orderCount++;
1908+ }
1909+ }
1910+ assertEquals("Customer 1 now has 2 orders in 2013", 2, orderCount);
1911+ return true;
1912+ }
1913+ });
1914+ }
1915+
1916+ @Test
1917+ public void insertNewOrderExpectFailure() throws Exception {
1918+ test(new TestExec() {
1919+ public boolean exec() throws Exception {
1920+ final Customer customer = new DirectIterableImpl<Customer>(
1921+ Customer.class, "customers", this).where("cid=1").single();
1922+ final Order newOrder = customer.getOrders().newInstance();
1923+ newOrder.setOid(101); // will collide
1924+ newOrder.setOdate(new java.sql.Date(System.currentTimeMillis()));
1925+ try {
1926+ newOrder.save();
1927+ fail("Should have failed");
1928+ } catch (DirectException e) {
1929+ assertTrue("Should wrap a SQLException",
1930+ SQLException.class.isAssignableFrom(e.getCause().getClass()));
1931+ } catch (Exception e) {
1932+ fail("Wrong type of exception: " + e);
1933+ }
1934+ return false;
1935+ }
1936+ });
1937+ }
1938+
1939+ private void test(TestExec te) throws Exception {
1940+ JDBCConnection conn = (JDBCConnection) Direct.getContext().getConnection();
1941+ conn.beginTransaction();
1942+ boolean commit = false;
1943+ try {
1944+ commit = te.exec();
1945+ } finally {
1946+ if (commit) {
1947+ conn.commitTransaction();
1948+ }
1949+ }
1950+ }
1951+
1952+ interface TestExec extends DirectObject {
1953+ boolean exec() throws Exception;
1954+ }
1955+
1956+ private void loadDatabase() throws Exception {
1957+ File schemaFile = new File(RESOURCE_DIR, "schema.ddl");
1958+ if (schemaFile.exists()) {
1959+ LOG.info("Loading " + schemaFile);
1960+ loadSchemaFile(SCHEMA_NAME, schemaFile);
1961+ }
1962+ for (File data : RESOURCE_DIR.listFiles(new RegexFilenameFilter(".*\\.dat"))) {
1963+ LOG.info("Loading " + data);
1964+ loadDataFile(SCHEMA_NAME, data);
1965+ }
1966+ }
1967+}
1968
1969=== modified file 'src/test/java/com/akiban/rest/RestServiceScriptsIT.java'
1970--- src/test/java/com/akiban/rest/RestServiceScriptsIT.java 2013-04-23 01:07:03 +0000
1971+++ src/test/java/com/akiban/rest/RestServiceScriptsIT.java 2013-04-26 02:16:29 +0000
1972@@ -61,7 +61,8 @@
1973 /**
1974 * Scripted tests for REST end-points. Code was largely copied from
1975 * RestServiceFilesIT. Difference is that this version finds files with the
1976- * suffix ".script" and executes the command stream located in them. Commands are:
1977+ * suffix ".script" and executes the command stream located in them. Commands
1978+ * are:
1979 *
1980 * <pre>
1981 * GET address
1982@@ -77,6 +78,8 @@
1983 * HEADERS expected
1984 * EMPTY
1985 * NOTEMPTY
1986+ * SHOW
1987+ * DEBUG
1988 * </pre>
1989 *
1990 * where address is a path relative the resource end-point, content is a string
1991@@ -93,10 +96,21 @@
1992 * POST /builder/implode/test.customers @
1993 * </pre>
1994 *
1995+ * The SHOW and DEBUG commands are useful for debugging. SHOW simply prints out
1996+ * the actual content of the last REST response. The DEBUG command calls the
1997+ * static method {@link #debug(int)}. You can set a debugger breakpoint inside
1998+ * that method.
1999+ *
2000 * @author peter
2001 */
2002 @RunWith(NamedParameterizedRunner.class)
2003 public class RestServiceScriptsIT extends ITBase {
2004+
2005+ private static void debug(int lineNumber) {
2006+ // Set a breakpoint here to debug on DEBUG statements
2007+ System.out.println("DEBUG executed on line " + lineNumber);
2008+ }
2009+
2010 private static final Logger LOG = LoggerFactory.getLogger(RestServiceScriptsIT.class.getName());
2011
2012 private static final File RESOURCE_DIR = new File("src/test/resources/"
2013@@ -241,7 +255,7 @@
2014
2015 switch (command) {
2016 case "DEBUG":
2017- System.out.println("DEBUG executed on line " + lineNumber);
2018+ debug(lineNumber);
2019 break;
2020 case "GET":
2021 case "DELETE": {
2022@@ -278,7 +292,7 @@
2023 error("Missing argument");
2024 continue;
2025 }
2026- String contents = value(line, 2);
2027+ String contents = value(line, 2);
2028 executeRestCall(command, pieces[1], contents);
2029 break;
2030 }
2031@@ -295,6 +309,7 @@
2032 continue;
2033 }
2034 if (!result.output.contains(value(line, 1))) {
2035+ LOG.error("Incorrect value - actual returned value is:\n{}", result.output);
2036 error("Incorrect response");
2037 }
2038 break;
2039@@ -323,6 +338,11 @@
2040 error("Expected empty response");
2041 }
2042 break;
2043+ case "SHOW":
2044+ int status = result.conn == null ? -1 : ((ContentExchange)result.conn).getResponseStatus();
2045+ System.out.printf("At line %d the most recent response status is %d. " + "The value is:\n%s\n",
2046+ lineNumber, status, result.output);
2047+ break;
2048 default:
2049 result.conn = null;
2050 error("Unknown script command '" + command + "'");
2051@@ -356,10 +376,7 @@
2052 result.output = getOutput(result.conn);
2053 } catch (Exception e) {
2054 result.output = e.toString();
2055- } finally {
2056- if (result.conn != null) {
2057- fullyDisconnect(result.conn);
2058- }
2059+ fullyDisconnect(result.conn);
2060 }
2061 }
2062
2063@@ -399,6 +416,7 @@
2064
2065 private void compareStrings(String assertMsg, String expected, String actual) {
2066 if (!expected.equals(actual)) {
2067+ LOG.error("Incorrect value - actual returned value is:\n{}", actual);
2068 error(assertMsg, diff(expected, actual));
2069 }
2070 }
2071
2072=== modified file 'src/test/java/com/akiban/server/service/restdml/DirectServiceTest.java'
2073--- src/test/java/com/akiban/server/service/restdml/DirectServiceTest.java 2013-04-04 21:46:56 +0000
2074+++ src/test/java/com/akiban/server/service/restdml/DirectServiceTest.java 2013-04-26 02:16:29 +0000
2075@@ -19,8 +19,6 @@
2076
2077 import org.junit.Test;
2078
2079-import com.akiban.rest.resources.DirectResource;
2080-
2081 public class DirectServiceTest {
2082
2083 @Test
2084
2085=== added directory 'src/test/resources/com/akiban/direct'
2086=== added file 'src/test/resources/com/akiban/direct/addresses.dat'
2087--- src/test/resources/com/akiban/direct/addresses.dat 1970-01-01 00:00:00 +0000
2088+++ src/test/resources/com/akiban/direct/addresses.dat 2013-04-26 02:16:29 +0000
2089@@ -0,0 +1,2 @@
2090+101 1 MA Boston
2091+201 2 NY New York
2092
2093=== added file 'src/test/resources/com/akiban/direct/customers.dat'
2094--- src/test/resources/com/akiban/direct/customers.dat 1970-01-01 00:00:00 +0000
2095+++ src/test/resources/com/akiban/direct/customers.dat 2013-04-26 02:16:29 +0000
2096@@ -0,0 +1,4 @@
2097+1 John Smith
2098+2 Willy Jones
2099+3 Jane Smith
2100+4 Jonathan Smyth
2101
2102=== added file 'src/test/resources/com/akiban/direct/items.dat'
2103--- src/test/resources/com/akiban/direct/items.dat 1970-01-01 00:00:00 +0000
2104+++ src/test/resources/com/akiban/direct/items.dat 2013-04-26 02:16:29 +0000
2105@@ -0,0 +1,3 @@
2106+1011 101 1234
2107+1012 101 4567
2108+2011 201 9876
2109
2110=== added file 'src/test/resources/com/akiban/direct/orders.dat'
2111--- src/test/resources/com/akiban/direct/orders.dat 1970-01-01 00:00:00 +0000
2112+++ src/test/resources/com/akiban/direct/orders.dat 2013-04-26 02:16:29 +0000
2113@@ -0,0 +1,3 @@
2114+101 1 2011-03-01
2115+102 1 2011-03-02
2116+201 2 2011-03-03
2117
2118=== added file 'src/test/resources/com/akiban/direct/schema.ddl'
2119--- src/test/resources/com/akiban/direct/schema.ddl 1970-01-01 00:00:00 +0000
2120+++ src/test/resources/com/akiban/direct/schema.ddl 2013-04-26 02:16:29 +0000
2121@@ -0,0 +1,32 @@
2122+CREATE TABLE customers(
2123+ cid INT NOT NULL,
2124+ name varchar(256) COLLATE en_us_ci,
2125+ PRIMARY KEY(cid)
2126+);
2127+
2128+CREATE TABLE addresses
2129+(
2130+ aid int NOT NULL,
2131+ cid int NOT NULL,
2132+ state CHAR(2),
2133+ city VARCHAR(100),
2134+ PRIMARY KEY(aid),
2135+ GROUPING FOREIGN KEY (cid) REFERENCES customers(cid)
2136+);
2137+
2138+CREATE TABLE orders(
2139+ oid INT NOT NULL,
2140+ cid INT,
2141+ odate DATE,
2142+ PRIMARY KEY(oid),
2143+ GROUPING FOREIGN KEY(cid) REFERENCES customers(cid)
2144+);
2145+
2146+CREATE TABLE items(
2147+ iid INT NOT NULL,
2148+ oid INT,
2149+ sku VARCHAR(8),
2150+ PRIMARY KEY(iid),
2151+ GROUPING FOREIGN KEY(oid) REFERENCES orders(oid)
2152+);
2153+
2154
2155=== modified file 'src/test/resources/com/akiban/rest/direct/direct-create.body'
2156--- src/test/resources/com/akiban/rest/direct/direct-create.body 2013-04-17 22:35:25 +0000
2157+++ src/test/resources/com/akiban/rest/direct/direct-create.body 2013-04-26 02:16:29 +0000
2158@@ -1,26 +1,14 @@
2159 function _register(registrar) {
2160 registrar.register(
2161 "method=GET path=cnames function=customer_Names in=(QP:prefix String required) out=String");
2162-
2163- registrar.register(
2164- "method=GET path=oids/(\\d*) function=order_Numbers in=(pp:1 int required) out=String");
2165 };
2166
2167 function customer_Names(s) {
2168 var result = s;
2169 var extent = Packages.com.akiban.direct.Direct.context.extent;
2170+
2171 for (customer in Iterator(extent.customers)) {
2172 result += "," + customer.name;
2173 }
2174 return result;
2175 }
2176-
2177-function order_Numbers(cid) {
2178- var result = s;
2179- var extent = Packages.com.akiban.direct.Direct.context.extent;
2180- var customer = extent.getCustomer(cid);
2181- for (order in customer.orders) {
2182- result += "," + order.oid;
2183- }
2184- return result;
2185-}
2186
2187=== modified file 'src/test/resources/com/akiban/rest/direct/direct-create.expected'
2188--- src/test/resources/com/akiban/rest/direct/direct-create.expected 2013-04-04 21:46:56 +0000
2189+++ src/test/resources/com/akiban/rest/direct/direct-create.expected 2013-04-26 02:16:29 +0000
2190@@ -1,3 +1,3 @@
2191-{"functions":2}
2192+{"functions":1}
2193
2194
2195
2196=== modified file 'src/test/resources/com/akiban/rest/direct/direct-demo.js'
2197--- src/test/resources/com/akiban/rest/direct/direct-demo.js 2013-04-04 16:07:24 +0000
2198+++ src/test/resources/com/akiban/rest/direct/direct-demo.js 2013-04-26 02:16:29 +0000
2199@@ -38,7 +38,9 @@
2200 }
2201
2202 function computeTotalCompensation(empno, start, end) {
2203- var emp = com.akiban.direct.Direct.context.extent.getEmployee(empno);
2204+ var extent = com.akiban.direct.Direct.context.extent;
2205+ var emp = extent.getEmployee(empno);
2206+ var newSalary = emp.salaries.newInstance();
2207 println("Computing total compensation for employee " + empno);
2208 var total = 0;
2209 var today = new Date();
2210
2211=== added file 'src/test/resources/com/akiban/rest/direct/direct-update.body'
2212--- src/test/resources/com/akiban/rest/direct/direct-update.body 1970-01-01 00:00:00 +0000
2213+++ src/test/resources/com/akiban/rest/direct/direct-update.body 2013-04-26 02:16:29 +0000
2214@@ -0,0 +1,35 @@
2215+function _register(registrar) {
2216+ registrar.register(
2217+ "method=GET path=cnames function=customerNames in=(QP:prefix String required) out=String");
2218+ registrar.register(
2219+ "method=PUT path=cadd/(\\\\d+) function=addCustomer in=(PP:1 int required, JSON:name string required) out=void");
2220+ registrar.register(
2221+ "method=POST path=cchange function=changeCustomerName in=(JSON:cid int required, JSON:name string required) out=void");
2222+};
2223+
2224+function customerNames(s) {
2225+ var result = s;
2226+ var extent = Packages.com.akiban.direct.Direct.context.extent;
2227+
2228+ for (customer in Iterator(extent.customers)) {
2229+ result += "," + customer.name;
2230+ }
2231+ return result;
2232+}
2233+
2234+function addCustomer(cid, name) {
2235+ var extent = Packages.com.akiban.direct.Direct.context.extent;
2236+ var customer = extent.customers.newInstance();
2237+ customer.cid = cid;
2238+ customer.name = name;
2239+ customer.save();
2240+}
2241+
2242+function changeCustomerName(cid, name) {
2243+ var extent = Packages.com.akiban.direct.Direct.context.extent;
2244+ var customer = extent.getCustomer(cid);
2245+ customer.setName(name);
2246+ customer.save();
2247+}
2248+
2249+
2250
2251=== modified file 'src/test/resources/com/akiban/rest/direct/direct-xref.expected'
2252--- src/test/resources/com/akiban/rest/direct/direct-xref.expected 2013-03-20 18:20:17 +0000
2253+++ src/test/resources/com/akiban/rest/direct/direct-xref.expected 2013-04-26 02:16:29 +0000
2254@@ -12,6 +12,5 @@
2255 ["getCustomer()","Customer"],
2256 ["getItem(iid)","Item"],
2257 ["getItems()",""],
2258- ["copy()","Order"],
2259 ["save()",""]]
2260 }
2261
2262=== modified file 'src/test/resources/com/akiban/rest/direct/test-basic-direct-functions.script'
2263--- src/test/resources/com/akiban/rest/direct/test-basic-direct-functions.script 2013-04-18 16:27:10 +0000
2264+++ src/test/resources/com/akiban/rest/direct/test-basic-direct-functions.script 2013-04-26 02:16:29 +0000
2265@@ -1,11 +1,11 @@
2266 # Install a library with two endpoints
2267 #
2268 PUT /direct/library?module=test.proc1&language=Javascript @direct-create.body
2269-EQUALS {"functions":2}\n
2270+EQUALS {"functions":1}\n
2271
2272 # Delete the library
2273 #
2274-DELETE /direct/library?module=test.proc1&language=Javascript
2275+DELETE /direct/library?module=test.proc1
2276 EQUALS {"functions":0}\n
2277
2278 # Reinstall it
2279@@ -26,5 +26,4 @@
2280 # Attempt to invoke end point should return a 404
2281 #
2282 GET /direct/call/test.cnames?prefix=SomeOtherParameterValue
2283-CONTAINS Not Found
2284-CONTAINS 404
2285+HEADERS responseCode:404
2286
2287=== modified file 'src/test/resources/com/akiban/rest/direct/test-direct-registration-errors.script'
2288--- src/test/resources/com/akiban/rest/direct/test-direct-registration-errors.script 2013-04-18 03:52:16 +0000
2289+++ src/test/resources/com/akiban/rest/direct/test-direct-registration-errors.script 2013-04-26 02:16:29 +0000
2290@@ -1,7 +1,7 @@
2291 # Normal registration
2292 #
2293 PUT /direct/library?module=test.proc1&language=Javascript @direct-create.body
2294-EQUALS {"functions":2}\n
2295+EQUALS {"functions":1}\n
2296
2297 # Attempt to register an invalid specification
2298 #
2299@@ -11,7 +11,7 @@
2300 # Register a script with no _register function
2301 #
2302 PUT /direct/library?module=test.proc2&language=Javascript @missing-direct-create.body
2303-EQUALS {"functions":2}\n
2304+EQUALS {"functions":1}\n
2305
2306 # Verify a valid script is still available
2307 #
2308
2309=== modified file 'src/test/resources/com/akiban/rest/direct/test-invalid-endpoint-errors.script'
2310--- src/test/resources/com/akiban/rest/direct/test-invalid-endpoint-errors.script 2013-04-18 16:27:10 +0000
2311+++ src/test/resources/com/akiban/rest/direct/test-invalid-endpoint-errors.script 2013-04-26 02:16:29 +0000
2312@@ -1,45 +1,38 @@
2313 # Normal registration
2314 #
2315 PUT /direct/library?module=test.proc1&language=Javascript @direct-create.body
2316-EQUALS {"functions":2}\n
2317+EQUALS {"functions":1}\n
2318
2319 # Try mismatched methods - expect 404 errors
2320 #
2321 PUT /direct/call/test.cnames?prefix=SomeParameterValue @
2322-CONTAINS 404
2323-CONTAINS Not Found
2324+HEADERS responseCode:404
2325 POST /direct/call/test.cnames?prefix=SomeParameterValue @
2326-CONTAINS 404
2327-CONTAINS Not Found
2328+HEADERS responseCode:404
2329 DELETE /direct/call/test.cnames?prefix=SomeParameterValue @
2330-CONTAINS 404
2331-CONTAINS Not Found
2332+HEADERS responseCode:404
2333
2334 PUT /direct/library?module=test.proc1&language=Javascript function _register(registrar) {registrar.register("method=GET path=x function=x in=(JSON:prefix String required) out=String");};
2335 GET /direct/call/test.x|text/plain
2336-CONTAINS 404
2337-CONTAINS Not Found
2338+HEADERS responseCode:404
2339
2340 # Mismatched method GET / POST
2341 #
2342 PUT /direct/library?module=test.proc1&language=Javascript function _register(registrar) {registrar.register("method=GET path=x function=x in=(JSON:prefix String required) out=String");};
2343 POST /direct/call/test.x|text/plain {"prefix": "SomeParamaterValue"}
2344-CONTAINS 404
2345-CONTAINS Not Found
2346+HEADERS responseCode:404
2347
2348 # Mismatched method POST / PUT
2349 #
2350 PUT /direct/library?module=test.proc1&language=Javascript function _register(registrar) {registrar.register("method=POST path=x function=x in=(JSON:prefix String required) out=String");};
2351 PUT /direct/call/test.x|application/json {"prefix": "SomeParamaterValue"}
2352-CONTAINS 404
2353-CONTAINS Not Found
2354+HEADERS responseCode:404
2355
2356 # Mismatched request type application/json / text/plain
2357 #
2358 PUT /direct/library?module=test.proc1&language=Javascript function _register(registrar) {registrar.register("method=POST path=x function=x in=(JSON:prefix String required) out=String");};
2359 POST /direct/call/test.x|text/plain {"prefix": "SomeParamaterValue"}
2360-CONTAINS 404
2361-CONTAINS Not Found
2362+HEADERS responseCode:404
2363
2364
2365
2366
2367=== added file 'src/test/resources/com/akiban/rest/direct/test-updates.script'
2368--- src/test/resources/com/akiban/rest/direct/test-updates.script 1970-01-01 00:00:00 +0000
2369+++ src/test/resources/com/akiban/rest/direct/test-updates.script 2013-04-26 02:16:29 +0000
2370@@ -0,0 +1,31 @@
2371+# Install a library
2372+#
2373+PUT /direct/library?module=test.updates1&language=javascript @direct-update.body
2374+EQUALS {"functions":3}\n
2375+
2376+# Verify data access
2377+#
2378+GET /direct/call/test.cnames?prefix=SomeParameterValue
2379+EQUALS SomeParameterValue,John Smith,Willy Jones,Jane Smith,Jonathan Smyth\n
2380+
2381+# Insert a new customer
2382+#
2383+PUT /direct/call/test.cadd/5 {"name":"Mary Johnson"}
2384+HEADERS responseCode:200
2385+GET /direct/call/test.cnames?prefix=SomeParameterValue
2386+EQUALS SomeParameterValue,John Smith,Willy Jones,Jane Smith,Jonathan Smyth,Mary Johnson\n
2387+
2388+# Modify customer name
2389+#
2390+POST /direct/call/test.cchange {"cid":5, "name":"Mary Johnston"}
2391+HEADERS responseCode:200
2392+GET /direct/call/test.cnames?prefix=SomeParameterValue
2393+EQUALS SomeParameterValue,John Smith,Willy Jones,Jane Smith,Jonathan Smyth,Mary Johnston\n
2394+
2395+# Cause a duplicate key error
2396+#
2397+PUT /direct/call/test.cadd/5 {"name":"Mary Johnson"}
2398+CONTAINS Non-unique key
2399+HEADERS responseCode:409
2400+
2401+

Subscribers

People subscribed via source and target branches