Initial version of 'lightweight' GWT ORM

This is a "lightweight" (I use the term loosely since its actually
close to 3000 lines of Java code) ORM implementation designed for
use against both JDBC and a local "offline" browser database such
as that offered by Google Gears or HTML 5.

The following 3rd party libraries are also included:

  * ObjectWeb ASM 3.1
  * ANTLR 3.1.1
  * H2 2008-11-07

Signed-off-by: Shawn O. Pearce <sop@google.com>
diff --git a/.classpath b/.classpath
new file mode 100644
index 0000000..28ff882
--- /dev/null
+++ b/.classpath
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="src" path="test"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/GWT"/>
+	<classpathentry kind="lib" path="lib/jdbc-h2.jar"/>
+	<classpathentry kind="lib" path="lib/antlr.jar"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
+	<classpathentry kind="lib" path="lib/asm.jar" sourcepath="lib/asm_src.zip"/>
+	<classpathentry kind="output" path="classes"/>
+</classpath>
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..bbe8a66
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+/.bin
+/classes
+/lib/*_src.zip
+/lib/gwtorm.jar
+/config.mak
+/src/com/google/gwtorm/schema/Query.tokens
+/src/com/google/gwtorm/schema/QueryLexer.java
+/src/com/google/gwtorm/schema/QueryParser.java
+/src/com/google/gwtorm/schema/Query__.g
diff --git a/.project b/.project
new file mode 100644
index 0000000..7c95189
--- /dev/null
+++ b/.project
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<projectDescription>
+   <name>GwtOrm</name>
+   <comment>GWT-ORM</comment>
+   <projects/>
+   <buildSpec>
+       <buildCommand>
+           <name>org.eclipse.jdt.core.javabuilder</name>
+           <arguments/>
+       </buildCommand>
+   </buildSpec>
+   <natures>
+       <nature>org.eclipse.jdt.core.javanature</nature>
+   </natures>
+</projectDescription>
diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..82eb859
--- /dev/null
+++ b/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,3 @@
+#Tue Sep 02 16:59:24 PDT 2008
+eclipse.preferences.version=1
+encoding/<project>=UTF-8
diff --git a/.settings/org.eclipse.core.runtime.prefs b/.settings/org.eclipse.core.runtime.prefs
new file mode 100644
index 0000000..8667cfd
--- /dev/null
+++ b/.settings/org.eclipse.core.runtime.prefs
@@ -0,0 +1,3 @@
+#Tue Sep 02 16:59:24 PDT 2008
+eclipse.preferences.version=1
+line.separator=\n
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..059dd5a
--- /dev/null
+++ b/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,268 @@
+#Fri Nov 14 18:42:47 PST 2008
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.5
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.5
+org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_assignment=16
+org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
+org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
+org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
+org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_after_package=1
+org.eclipse.jdt.core.formatter.blank_lines_before_field=0
+org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
+org.eclipse.jdt.core.formatter.blank_lines_before_imports=0
+org.eclipse.jdt.core.formatter.blank_lines_before_member_type=0
+org.eclipse.jdt.core.formatter.blank_lines_before_method=1
+org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
+org.eclipse.jdt.core.formatter.blank_lines_before_package=0
+org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
+org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=2
+org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
+org.eclipse.jdt.core.formatter.comment.format_block_comments=true
+org.eclipse.jdt.core.formatter.comment.format_header=true
+org.eclipse.jdt.core.formatter.comment.format_html=true
+org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
+org.eclipse.jdt.core.formatter.comment.format_line_comments=true
+org.eclipse.jdt.core.formatter.comment.format_source_code=true
+org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false
+org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
+org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
+org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
+org.eclipse.jdt.core.formatter.comment.line_length=80
+org.eclipse.jdt.core.formatter.compact_else_if=true
+org.eclipse.jdt.core.formatter.continuation_indentation=2
+org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
+org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
+org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_empty_lines=false
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
+org.eclipse.jdt.core.formatter.indentation.size=4
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
+org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true
+org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.lineSplit=80
+org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
+org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=3
+org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=false
+org.eclipse.jdt.core.formatter.tabulation.char=space
+org.eclipse.jdt.core.formatter.tabulation.size=2
+org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
+org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
diff --git a/.settings/org.eclipse.jdt.ui.prefs b/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 0000000..f37f6f0
--- /dev/null
+++ b/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,9 @@
+#Tue Sep 02 17:00:18 PDT 2008
+eclipse.preferences.version=1
+formatter_profile=_Google Format
+formatter_settings_version=11
+org.eclipse.jdt.ui.ignorelowercasenames=true
+org.eclipse.jdt.ui.importorder=com.google;com;junit;net;org;java;javax;
+org.eclipse.jdt.ui.ondemandthreshold=99
+org.eclipse.jdt.ui.staticondemandthreshold=99
+org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates/>
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/GwtOrm-AllTests.launch b/GwtOrm-AllTests.launch
new file mode 100644
index 0000000..7c5ef6d
--- /dev/null
+++ b/GwtOrm-AllTests.launch
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<launchConfiguration type="org.eclipse.jdt.junit.launchconfig">
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
+<listEntry value="/GwtOrm"/>
+</listAttribute>
+<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
+<listEntry value="4"/>
+</listAttribute>
+<stringAttribute key="org.eclipse.jdt.junit.CONTAINER" value="=GwtOrm"/>
+<booleanAttribute key="org.eclipse.jdt.junit.KEEPRUNNING_ATTR" value="false"/>
+<stringAttribute key="org.eclipse.jdt.junit.TESTNAME" value=""/>
+<stringAttribute key="org.eclipse.jdt.junit.TEST_KIND" value="org.eclipse.jdt.junit.loader.junit4"/>
+<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value=""/>
+<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="GwtOrm"/>
+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dgwtorm.debugCodeGen=true"/>
+</launchConfiguration>
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..80c0b97
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,78 @@
+# Lightweight ORM for Google Web Toolkit
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Define GWT_SDK to the location of the Google Web Toolkit SDK.
+#
+# Define GWT_OS to your operating system, e.g. 'linux', 'mac'.
+#
+
+uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
+
+JAVA       = java
+JAVAC      = javac
+GWT_OS     = unknown
+
+ifeq ($(uname_S),Darwin)
+	GWT_OS = mac
+endif
+ifeq ($(uname_S),Linux)
+	GWT_OS = linux
+endif
+ifeq ($(uname_S),Cygwin)
+	GWT_OS = win
+endif
+
+-include config.mak
+
+GWT_CP     = $(GWT_SDK)/gwt-user.jar:$(GWT_SDK)/gwt-dev-$(GWT_OS).jar
+OUR_CP     = ../lib/antlr.jar:../lib/asm.jar
+MY_JAR     = lib/gwtorm.jar
+MY_JAVA    = $(shell find src -name \*.java)
+MY_GWT_XML = com/google/gwtorm/GWTORM.gwt.xml
+
+QUERY_G    = src/com/google/gwtorm/schema/Query.g
+QUERY_JAVA = src/com/google/gwtorm/schema/QueryParser.java
+
+all: $(MY_JAR)
+
+clean:
+	rm -f $(MY_JAR)
+	rm -f $(QUERY_JAVA)
+	rm -f src/com/google/gwtorm/schema/Query.tokens
+	rm -f src/com/google/gwtorm/schema/QueryLexer.java
+	rm -f src/com/google/gwtorm/schema/Query__.g
+	rm -rf classes .bin
+
+$(QUERY_JAVA): $(QUERY_G)
+	$(JAVA) -cp lib/antlr.jar org.antlr.Tool $(QUERY_G)
+
+$(MY_JAR): $(MY_JAVA) src/$(MY_GWT_XML) $(QUERY_JAVA)
+	rm -rf .bin
+	mkdir .bin
+	cd src && $(JAVAC) \
+		-encoding utf-8 \
+		-source 1.5 \
+		-target 1.5 \
+		-g \
+		-cp $(GWT_CP):$(OUR_CP) \
+		-d ../.bin \
+		$(patsubst src/%,%,$(MY_JAVA))
+	cd .bin && jar cf ../$(MY_JAR) .
+	cd src && jar uf ../$(MY_JAR) .
+	rm -rf .bin
+
+.PHONY: all
+.PHONY: clean
diff --git a/README_ECLIPSE b/README_ECLIPSE
new file mode 100644
index 0000000..a0da056
--- /dev/null
+++ b/README_ECLIPSE
@@ -0,0 +1,21 @@
+Eclipse Setup
+=============
+
+Compile once using "make" to ensure the ANTLR parser is generated.
+
+
+User Library
+------------
+
+  Window > Preferences
+  Java > Build Path > User Libraries
+
+Create a User Library called "GWT":
+
+  New
+
+  Name: GWT
+  Add JARs...
+
+  * Select gwt-user.jar from the $(GWT_SDK) directory.
+  * Select gwt-dev-$(GWT_OS).jar from the $(GWT_SDK) directory.
diff --git a/lib/.gitignore b/lib/.gitignore
new file mode 100644
index 0000000..f852cd5
--- /dev/null
+++ b/lib/.gitignore
@@ -0,0 +1,2 @@
+/gwtorm.jar
+/*_src.zip
diff --git a/lib/antlr.jar b/lib/antlr.jar
new file mode 100644
index 0000000..da03a67
--- /dev/null
+++ b/lib/antlr.jar
Binary files differ
diff --git a/lib/antlr_LICENSE b/lib/antlr_LICENSE
new file mode 100644
index 0000000..6cfe9b0
--- /dev/null
+++ b/lib/antlr_LICENSE
@@ -0,0 +1,29 @@
+ANTLR 3.1.1
+-----------
+
+[The "BSD licence"]
+Copyright (c) 2003-2008 Terence Parr
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+ 1. Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+    notice, this list of conditions and the following disclaimer in the
+    documentation and/or other materials provided with the distribution.
+ 3. The name of the author may not be used to endorse or promote products
+    derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/lib/asm.jar b/lib/asm.jar
new file mode 100644
index 0000000..b3baf3f
--- /dev/null
+++ b/lib/asm.jar
Binary files differ
diff --git a/lib/asm_LICENSE b/lib/asm_LICENSE
new file mode 100644
index 0000000..2b89056
--- /dev/null
+++ b/lib/asm_LICENSE
@@ -0,0 +1,30 @@
+ ObjectWeb ASM 3.1
+ -----------------
+
+ ASM: a very small and fast Java bytecode manipulation framework
+ Copyright (c) 2000-2005 INRIA, France Telecom
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+    notice, this list of conditions and the following disclaimer in the
+    documentation and/or other materials provided with the distribution.
+ 3. Neither the name of the copyright holders nor the names of its
+    contributors may be used to endorse or promote products derived from
+    this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/lib/jdbc-h2.jar b/lib/jdbc-h2.jar
new file mode 100644
index 0000000..e9f2d40
--- /dev/null
+++ b/lib/jdbc-h2.jar
Binary files differ
diff --git a/lib/jdbc-h2_LICENSE.html b/lib/jdbc-h2_LICENSE.html
new file mode 100644
index 0000000..a6c443e
--- /dev/null
+++ b/lib/jdbc-h2_LICENSE.html
@@ -0,0 +1,703 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<!-- H2 Database 2008-11-07 -->
+<!-- 
+Copyright 2004-2008 H2 Group. Multiple-Licensed under the H2 License, Version 1.0,
+and under the Eclipse Public License, Version 1.0
+(http://h2database.com/html/license.html). 
+Initial Developer: H2 Group
+-->
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
+<head><meta http-equiv="Content-Type" content="text/html;charset=utf-8" /><title>
+License
+</title><link rel="stylesheet" type="text/css" href="stylesheet.css" />
+<!-- [search] { -->
+<script type="text/javascript" src="navigation.js"></script>
+</head><body onload="frameMe();">
+<table class="content"><tr class="content"><td class="content"><div class="contentDiv">
+<!-- } -->
+
+<h1>License</h1>
+
+<h2>Summary and License FAQ</h2>
+<p>
+H2 is dual licensed and available under a modified version of the 
+MPL 1.1  (<a href="http://www.mozilla.org/MPL">Mozilla Public License</a>)
+or EPL 1.0  (<a href="http://opensource.org/licenses/eclipse-1.0.php">Eclipse Public License</a>).
+The changes are <em class="u">underlined</em>.
+There is a License FAQ for both the MPL and the EPL, most of that is applicable to the H2 License as well.
+</p>
+<ul>
+<li>You can use H2 for free. You can integrate it into your application (including commercial applications), 
+    and you can distribute it.
+</li><li>Files containing only your code are not covered by this license (it is 'commercial friendly').
+</li><li>Modifications to the H2 source code must be published.
+</li><li>You don't need to provide the source code of H2 if you did not modify anything.
+</li></ul>
+
+<p>
+However, nobody is allowed to rename H2, modify it a little, and sell it as a database engine without telling the customers it is in fact H2. 
+This happened to HSQLDB, when a company called 'bungisoft' copied HSQLDB, renamed it to 'RedBase', and tried to sell it, 
+hiding the fact that it was, in fact, just HSQLDB. At this time, it seems 'bungisoft' does not exist any more, but you can use the 
+Wayback Machine of http://www.archive.org and look for old web pages of http://www.bungisoft.com .
+</p><p>
+About porting the source code to another language (for example C# or C++): Converted source code (even if done manually) stays under the same 
+copyright and license as the original code. The copyright of the ported source code does not (automatically) go to the person who ported the code.
+</p>
+
+<h2>H2 License, Version 1.0</h2>
+
+ <h3 id="section-1">1. Definitions</h3>
+  <p id="section-1.0.1"><b>1.0.1. "Commercial Use"</b>
+  means distribution or otherwise making the Covered Code available to a third party.
+  </p>
+  <p id="section-1.1"><b>1.1. "Contributor"</b>
+  means each entity that creates or contributes to the creation of Modifications.
+  </p>
+  <p id="section-1.2"><b>1.2. "Contributor Version"</b>
+  means the combination of the Original Code, prior Modifications used by a Contributor,
+   and the Modifications made by that particular Contributor.
+  </p>
+  <p id="section-1.3"><b>1.3. "Covered Code"</b>
+  means the Original Code or Modifications or the combination of the Original Code and
+   Modifications, in each case including portions thereof.
+  </p>
+  <p id="section-1.4"><b>1.4. "Electronic Distribution Mechanism"</b>
+  means a mechanism generally accepted in the software development community for the
+   electronic transfer of data.
+  </p>
+   <p id="section-1.5"><b>1.5. "Executable"</b>
+  means Covered Code in any form other than Source Code.
+ </p>
+   <p id="section-1.6"><b>1.6. "Initial Developer"</b>
+  means the individual or entity identified as the Initial Developer in the Source Code
+   notice required by <a href="#exhibit-a">Exhibit A</a>.
+    </p>
+  <p id="section-1.7"><b>1.7. "Larger Work"</b>
+  means a work which combines Covered Code or portions thereof with code not governed
+   by the terms of this License.
+ </p>   
+  <p id="section-1.8"><b>1.8. "License"</b>
+  means this document.
+ </p>  
+  <p id="section-1.8.1"><b>1.8.1. "Licensable"</b>
+  means having the right to grant, to the maximum extent possible, whether at the
+   time of the initial grant or subsequently acquired, any and all of the rights
+   conveyed herein.
+ </p>   
+  <p id="section-1.9"><b>1.9. "Modifications"</b>
+    means any addition to or deletion from the substance or structure of either the
+    Original Code or any previous Modifications. When Covered Code is released as a
+    series of files, a Modification is:
+  </p>
+  <p id="section-1.9-a">1.9.a. Any addition to or deletion from the contents of a file
+     containing Original Code or previous Modifications.
+  </p>
+  <p id="section-1.9-b">1.9.b. Any new file that contains any part of the Original Code or
+     previous Modifications.
+  </p>
+  <p id="section-1.10"><b>1.10. "Original Code"</b>
+  means Source Code of computer software code which is described in the Source Code
+   notice required by <a href="#exhibit-a">Exhibit A</a> as Original Code, and which,
+   at the time of its release under this License is not already Covered Code governed
+   by this License.
+ </p>
+  <p id="section-1.10.1"><b>1.10.1. "Patent Claims"</b>
+  means any patent claim(s), now owned or hereafter acquired, including without
+   limitation, method, process, and apparatus claims, in any patent Licensable by
+   grantor.
+ </p>
+  <p id="section-1.11"><b>1.11. "Source Code"</b>
+  means the preferred form of the Covered Code for making modifications to it,
+   including all modules it contains, plus any associated interface definition files,
+   scripts used to control compilation and installation of an Executable, or source
+   code differential comparisons against either the Original Code or another well known,
+   available Covered Code of the Contributor's choice. The Source Code can be in a
+   compressed or archival form, provided the appropriate decompression or de-archiving
+   software is widely available for no charge.
+ </p>
+  <p id="section-1.12"><b>1.12. "You" (or "Your")</b>
+  means an individual or a legal entity exercising rights under, and complying with
+   all of the terms of, this License or a future version of this License issued under
+   <a href="#section-6.1">Section 6.1.</a> For legal entities, "You" includes any entity
+   which controls, is controlled by, or is under common control with You. For purposes of
+   this definition, "control" means (a) the power, direct or indirect, to cause the
+   direction or management of such entity, whether by contract or otherwise, or (b)
+   ownership of more than fifty percent (50%) of the outstanding shares or beneficial
+   ownership of such entity.
+ </p>
+
+ <h3 id="section-2">2. Source Code License</h3>
+ <h4 id="section-2.1">2.1. The Initial Developer Grant</h4>
+ <p>
+ The Initial Developer hereby grants You a world-wide, royalty-free, non-exclusive
+  license, subject to third party intellectual property claims:
+  </p>
+  <p id="section-2.1-a">2.1.a. under intellectual property rights (other than patent or
+   trademark) Licensable by Initial Developer to use, reproduce, modify, display, perform,
+   sublicense and distribute the Original Code (or portions thereof) with or without
+   Modifications, and/or as part of a Larger Work; and
+   </p>
+  <p id="section-2.1-b">2.1.b. under Patents Claims infringed by the making, using or selling
+   of Original Code, to make, have made, use, practice, sell, and offer for sale, and/or
+   otherwise dispose of the Original Code (or portions thereof).
+   </p>
+  <p id="section-2.1-c">2.1.c. the licenses granted in this Section 2.1
+   (<a href="#section-2.1-a">a</a>) and (<a href="#section-2.1-b">b</a>) are effective on
+   the date Initial Developer first distributes Original Code under the terms of this
+   License.
+   </p>
+  <p id="section-2.1-d">2.1.d. Notwithstanding Section 2.1 (<a href="#section-2.1-b">b</a>)
+   above, no patent license is granted: 1) for code that You delete from the Original Code;
+   2) separate from the Original Code; or 3) for infringements caused by: i) the
+   modification of the Original Code or ii) the combination of the Original Code with other
+   software or devices.
+ </p>
+
+ <h4 id="section-2.2">2.2. Contributor Grant</h4>
+ <p>
+ Subject to third party intellectual property claims, each Contributor hereby grants You
+  a world-wide, royalty-free, non-exclusive license
+  </p>
+  <p id="section-2.2-a">2.2.a. under intellectual property rights (other than patent or trademark)
+   Licensable by Contributor, to use, reproduce, modify, display, perform, sublicense and
+   distribute the Modifications created by such Contributor (or portions thereof) either on
+   an unmodified basis, with other Modifications, as Covered Code and/or as part of a Larger
+   Work; and
+ </p>
+ <p id="section-2.2-b">2.2.b. under Patent Claims infringed by the making, using, or selling of
+   Modifications made by that Contributor either alone and/or in combination with its
+   Contributor Version (or portions of such combination), to make, use, sell, offer for
+   sale, have made, and/or otherwise dispose of: 1) Modifications made by that Contributor
+   (or portions thereof); and 2) the combination of Modifications made by that Contributor
+   with its Contributor Version (or portions of such combination).
+  </p>
+  <p id="section-2.2-c">2.2.c. the licenses granted in Sections 2.2
+   (<a href="#section-2.2-a">a</a>) and 2.2 (<a href="#section-2.2-b">b</a>) are effective
+   on the date Contributor first makes Commercial Use of the Covered Code.
+  </p>
+  <p id="section-2.2-d">2.2.c. Notwithstanding Section 2.2 (<a href="#section-2.2-b">b</a>)
+   above, no patent license is granted: 1) for any code that Contributor has deleted from
+   the Contributor Version; 2) separate from the Contributor Version; 3) for infringements
+   caused by: i) third party modifications of Contributor Version or ii) the combination of
+   Modifications made by that Contributor with other software (except as part of the
+   Contributor Version) or other devices; or 4) under Patent Claims infringed by Covered Code
+   in the absence of Modifications made by that Contributor.
+ </p>
+
+ <h3 id="section-3">3. Distribution Obligations</h3>
+ <h4 id="section-3.1">3.1. Application of License</h4>
+ <p>
+ The Modifications which You create or to which You contribute are governed by the terms
+  of this License, including without limitation Section <a href="#section-2.2">2.2</a>. The
+  Source Code version of Covered Code may be distributed only under the terms of this License
+  or a future version of this License released under Section <a href="#section-6.1">6.1</a>,
+  and You must include a copy of this License with every copy of the Source Code You
+  distribute. You may not offer or impose any terms on any Source Code version that alters or
+  restricts the applicable version of this License or the recipients' rights hereunder.
+  However, You may include an additional document offering the additional rights described in
+  Section <a href="#section-3.5">3.5</a>.
+</p>
+  
+ <h4 id="section-3.2">3.2. Availability of Source Code</h4>
+ <p>
+ Any Modification which You create or to which You contribute must be made available in
+  Source Code form under the terms of this License either on the same media as an Executable
+  version or via an accepted Electronic Distribution Mechanism to anyone to whom you made an
+  Executable version available; and if made available via Electronic Distribution Mechanism,
+  must remain available for at least twelve (12) months after the date it initially became
+  available, or at least six (6) months after a subsequent version of that particular
+  Modification has been made available to such recipients. You are responsible for ensuring
+  that the Source Code version remains available even if the Electronic Distribution
+  Mechanism is maintained by a third party.
+  </p>
+  
+ <h4 id="section-3.3">3.3. Description of Modifications</h4>
+<p>
+ You must cause all Covered Code to which You contribute to contain a file documenting the
+  changes You made to create that Covered Code and the date of any change. You must include a
+  prominent statement that the Modification is derived, directly or indirectly, from Original
+  Code provided by the Initial Developer and including the name of the Initial Developer in
+  (a) the Source Code, and (b) in any notice in an Executable version or related documentation
+  in which You describe the origin or ownership of the Covered Code.
+  </p>
+  
+ <h4 id="section-3.4">3.4. Intellectual Property Matters</h4>
+ <p id="section-3.4-a"><b>3.4.a. Third Party Claims:</b>
+     If Contributor has knowledge that a license under a third party's intellectual property
+  rights is required to exercise the rights granted by such Contributor under Sections
+  <a href="#section-2.1">2.1</a> or <a href="#section-2.2">2.2</a>, Contributor must include a
+  text file with the Source Code distribution titled "LEGAL" which describes the claim and the
+  party making the claim in sufficient detail that a recipient will know whom to contact. If
+  Contributor obtains such knowledge after the Modification is made available as described in
+  Section <a href="#section-3.2">3.2</a>, Contributor shall promptly modify the LEGAL file in
+  all copies Contributor makes available thereafter and shall take other steps (such as
+  notifying appropriate mailing lists or newsgroups) reasonably calculated to inform those who
+  received the Covered Code that new knowledge has been obtained.
+ </p>
+ <p id="section-3.4-b"><b>3.4.b. Contributor APIs:</b> 
+ If Contributor's Modifications include an application programming interface and Contributor
+  has knowledge of patent licenses which are reasonably necessary to implement that
+  API, Contributor must also include this information in the legal file.
+ </p>
+ <p id="section-3.4-c"><b>3.4.c. Representations:</b>
+ Contributor represents that, except as disclosed pursuant to Section 3.4
+  (<a href="#section-3.4-a">a</a>) above, Contributor believes that Contributor's Modifications
+  are Contributor's original creation(s) and/or Contributor has sufficient rights to grant the
+  rights conveyed by this License.
+ </p>
+  
+ <h4 id="section-3.5">3.5. Required Notices</h4>
+ <p>
+ You must duplicate the notice in <a href="#exhibit-a">Exhibit A</a> in each file of the
+  Source Code. If it is not possible to put such notice in a particular Source Code file due to
+  its structure, then You must include such notice in a location (such as a relevant directory)
+  where a user would be likely to look for such a notice. If You created one or more
+  Modification(s) You may add your name as a Contributor to the notice described in
+  <a href="#exhibit-a">Exhibit A</a>. You must also duplicate this License in any documentation
+  for the Source Code where You describe recipients' rights or ownership rights relating to
+  Covered Code. You may choose to offer, and to charge a fee for, warranty, support, indemnity
+  or liability obligations to one or more recipients of Covered Code. However, You may do so
+  only on Your own behalf, and not on behalf of the Initial Developer or any Contributor. You
+  must make it absolutely clear than any such warranty, support, indemnity or liability
+  obligation is offered by You alone, and You hereby agree to indemnify the Initial Developer
+  and every Contributor for any liability incurred by the Initial Developer or such Contributor
+  as a result of warranty, support, indemnity or liability terms You offer.
+  </p>
+  
+ <h4 id="section-3.6">3.6. Distribution of Executable Versions</h4>
+ <p>
+ You may distribute Covered Code in Executable form only if the requirements of Sections
+  <a href="#section-3.1">3.1</a>, <a href="#section-3.2">3.2</a>,
+  <a href="#section-3.3">3.3</a>, <a href="#section-3.4">3.4</a> and
+  <a href="#section-3.5">3.5</a> have been met for that Covered Code, and if You include a
+  notice stating that the Source Code version of the Covered Code is available under the terms
+  of this License, including a description of how and where You have fulfilled the obligations
+  of Section <a href="#section-3.2">3.2</a>. The notice must be conspicuously included in any
+  notice in an Executable version, related documentation or collateral in which You describe
+  recipients' rights relating to the Covered Code. You may distribute the Executable version of
+  Covered Code or ownership rights under a license of Your choice, which may contain terms
+  different from this License, provided that You are in compliance with the terms of this
+  License and that the license for the Executable version does not attempt to limit or alter the
+  recipient's rights in the Source Code version from the rights set forth in this License. If
+  You distribute the Executable version under a different license You must make it absolutely
+  clear that any terms which differ from this License are offered by You alone, not by the
+  Initial Developer or any Contributor. You hereby agree to indemnify the Initial Developer and
+  every Contributor for any liability incurred by the Initial Developer or such Contributor as
+  a result of any such terms You offer.
+</p>  
+  
+ <h4 id="section-3.7">3.7. Larger Works</h4>
+ <p>
+ You may create a Larger Work by combining Covered Code with other code not governed by the
+  terms of this License and distribute the Larger Work as a single product. In such a case,
+  You must make sure the requirements of this License are fulfilled for the Covered Code.
+  </p>
+  
+ <h3 id="section-4">4. Inability to Comply Due to Statute or Regulation.</h3>
+ <p>
+ If it is impossible for You to comply with any of the terms of this License with respect to
+  some or all of the Covered Code due to statute, judicial order, or regulation then You must:
+  (a) comply with the terms of this License to the maximum extent possible; and (b) describe
+  the limitations and the code they affect. Such description must be included in the
+  <b>legal</b> file described in Section
+  <a href="#section-3.4">3.4</a> and must be included with all distributions of the Source Code.
+  Except to the extent prohibited by statute or regulation, such description must be
+  sufficiently detailed for a recipient of ordinary skill to be able to understand it.
+  </p>
+  
+ <h3 id="section-5">5. Application of this License.</h3>
+ <p>
+ This License applies to code to which the Initial Developer has attached the notice in
+  <a href="#exhibit-a">Exhibit A</a> and to related Covered Code.
+  </p>
+  
+ <h3 id="section-6">6. Versions of the License.</h3>
+
+ <h4 id="section-6.1">6.1. New Versions</h4>
+<p>
+ The <em class="u">H2 Group</em> may publish revised and/or new versions
+  of the License from time to time. Each version will be given a distinguishing version number.
+</p>
+  
+ <h4 id="section-6.2">6.2. Effect of New Versions</h4>
+ <p>
+ Once Covered Code has been published under a particular version of the License, You may
+  always continue to use it under the terms of that version. You may also choose to use such
+  Covered Code under the terms of any subsequent version of the License published by the <em class="u">H2 Group</em>.
+  No one other than the <em class="u">H2 Group</em> has the right to modify the terms applicable to Covered Code
+  created under this License.
+</p>
+  
+ <h4 id="section-6.3">6.3. Derivative Works</h4>
+ <p>
+ If You create or use a modified version of this License (which you may only do in order to
+ apply it to code which is not already Covered Code governed by this License), You must (a)
+ rename Your license so that the phrases <em class="u">"H2 Group", "H2"</em> 
+ or any confusingly similar phrase do not appear in your license (except to note that
+ your license differs from this License) and (b) otherwise make it clear that Your version of
+ the license contains terms which differ from the <em class="u">H2 License</em>. 
+ (Filling in the name of the Initial Developer, Original Code or Contributor in the
+ notice described in <a href="#exhibit-a">Exhibit A</a> shall not of themselves be deemed to
+ be modifications of this License.)
+</p>
+ 
+ <h3 id="section-7">7. Disclaimer of Warranty</h3>
+<p>
+Covered code is provided under this license on an "as is"
+  basis, without warranty of any kind, either expressed or implied, including, without
+  limitation, warranties that the covered code is free of defects, merchantable, fit for a
+  particular purpose or non-infringing. The entire risk as to the quality and performance of
+  the covered code is with you. Should any covered code prove defective in any respect, you
+  (not the initial developer or any other contributor) assume the cost of any necessary
+  servicing, repair or correction. This disclaimer of warranty constitutes an essential part
+  of this license. No use of any covered code is authorized hereunder except under this
+  disclaimer.
+  </p>
+  
+ <h3 id="section-8">8. Termination</h3>
+ <p id="section-8.1">8.1. This License and the rights granted hereunder will terminate
+  automatically if You fail to comply with terms herein and fail to cure such breach
+  within 30 days of becoming aware of the breach. All sublicenses to the Covered Code which
+  are properly granted shall survive any termination of this License. Provisions which, by
+  their nature, must remain in effect beyond the termination of this License shall survive.
+ </p>
+ 
+ <p id="section-8.2">8.2. If You initiate litigation by asserting a patent infringement
+  claim (excluding declaratory judgment actions) against Initial Developer or a Contributor
+  (the Initial Developer or Contributor against whom You file such action is referred to
+  as "Participant") alleging that:
+</p>
+  <p id="section-8.2-a">8.2.a. such Participant's Contributor Version directly or indirectly
+   infringes any patent, then any and all rights granted by such Participant to You under
+   Sections <a href="#section-2.1">2.1</a> and/or <a href="#section-2.2">2.2</a> of this
+   License shall, upon 60 days notice from Participant terminate prospectively, unless if
+   within 60 days after receipt of notice You either: (i) agree in writing to pay
+   Participant a mutually agreeable reasonable royalty for Your past and future use of
+   Modifications made by such Participant, or (ii) withdraw Your litigation claim with
+   respect to the Contributor Version against such Participant. If within 60 days of
+   notice, a reasonable royalty and payment arrangement are not mutually agreed upon in
+   writing by the parties or the litigation claim is not withdrawn, the rights granted by
+   Participant to You under Sections <a href="#section-2.1">2.1</a> and/or
+   <a href="#section-2.2">2.2</a> automatically terminate at the expiration of the 60 day
+   notice period specified above.
+  </p>
+  <p id="section-8.2-b">8.2.b. any software, hardware, or device, other than such Participant's
+   Contributor Version, directly or indirectly infringes any patent, then any rights
+   granted to You by such Participant under Sections 2.1(<a href="#section-2.1-b">b</a>)
+   and 2.2(<a href="#section-2.2-b">b</a>) are revoked effective as of the date You first
+   made, used, sold, distributed, or had made, Modifications made by that Participant.
+ </p>
+ 
+ <p id="section-8.3">8.3. If You assert a patent infringement claim against Participant
+  alleging that such Participant's Contributor Version directly or indirectly infringes
+  any patent where such claim is resolved (such as by license or settlement) prior to the
+  initiation of patent infringement litigation, then the reasonable value of the licenses
+  granted by such Participant under Sections <a href="#section-2.1">2.1</a> or
+  <a href="#section-2.2">2.2</a> shall be taken into account in determining the amount or
+  value of any payment or license.
+</p>
+
+ <p id="section-8.4">8.4. In the event of termination under Sections
+  <a href="#section-8.1">8.1</a> or <a href="#section-8.2">8.2</a> above, all end user
+  license agreements (excluding distributors and resellers) which have been validly
+  granted by You or any distributor hereunder prior to termination shall survive
+  termination.
+ </p>
+  
+<h3 id="section-9">9. Limitation of Liability</h3>
+<p>
+Under no circumstances and under no legal theory, whether
+  tort (including negligence), contract, or otherwise, shall you, the initial developer,
+  any other contributor, or any distributor of covered code, or any supplier of any of
+  such parties, be liable to any person for any indirect, special, incidental, or
+  consequential damages of any character including, without limitation, damages for loss
+  of goodwill, work stoppage, computer failure or malfunction, or any and all other
+  commercial damages or losses, even if such party shall have been informed of the
+  possibility of such damages. This limitation of liability shall not apply to liability
+  for death or personal injury resulting from such party's negligence to the extent
+  applicable law prohibits such limitation. Some jurisdictions do not allow the exclusion
+  or limitation of incidental or consequential damages, so this exclusion and limitation
+  may not apply to you.
+</p>
+  
+<h3 id="section-10">10. United States Government End Users</h3>
+<p>
+ The Covered Code is a "commercial item", as that term is defined in 48
+  C.F.R. 2.101 (October 1995), consisting of
+  "commercial computer software" and "commercial computer software documentation", as such
+  terms are used in 48 C.F.R. 12.212 (September 1995). Consistent with 48 C.F.R. 12.212 and 48 C.F.R.
+  227.7202-1 through 227.7202-4 (June 1995), all U.S. Government End Users
+  acquire Covered Code with only those rights set forth herein.
+</p>
+  
+<h3 id="section-11">11. Miscellaneous</h3>
+<p>
+This License represents the complete agreement concerning subject matter hereof. If
+  any provision of this License is held to be unenforceable, such provision shall be
+  reformed only to the extent necessary to make it enforceable. This License shall be
+  governed by <em class="u">Swiss</em> law provisions (except to the extent applicable law, if any,
+  provides otherwise), excluding its conflict-of-law provisions. With respect to
+  disputes in which at least one party is a citizen of, or an entity chartered or
+  registered to do business in <em class="u">Switzerland</em>, any litigation relating to
+  this License shall be subject to the jurisdiction of <em class="u">Switzerland</em>,  
+  with the losing party responsible for costs, including without limitation, court
+  costs and reasonable attorneys' fees and expenses. The application of the United
+  Nations Convention on Contracts for the International Sale of Goods is expressly
+  excluded. Any law or regulation which provides that the language of a contract
+  shall be construed against the drafter shall not apply to this License.
+</p>
+  
+<h3 id="section-12">12. Responsibility for Claims</h3>
+<p>
+As between Initial Developer and the Contributors, each party is responsible for
+claims and damages arising, directly or indirectly, out of its utilization of rights
+under this License and You agree to work with Initial Developer and Contributors to
+distribute such responsibility on an equitable basis. Nothing herein is intended or
+shall be deemed to constitute any admission of liability.
+</p>
+
+<h3 id="section-13">13. Multiple-Licensed Code</h3>
+<p>
+Initial Developer may designate portions of the Covered Code as
+"Multiple-Licensed". "Multiple-Licensed" means that the Initial Developer permits
+you to utilize portions of the Covered Code under Your choice of this 
+or the alternative licenses, if any, specified by the Initial Developer in the file
+described in <a href="#exhibit-a">Exhibit A</a>.
+</p>
+  
+<h3 id="exhibit-a">Exhibit A</h3>
+<pre>
+Multiple-Multiple-Licensed under the H2 License, Version 1.0,
+and under the Eclipse Public License, Version 1.0
+, and under the
+Eclipse Public License, Version 1.0 (http://h2database.com/html/license.html)
+Initial Developer: H2 Group
+</pre>
+
+<h2>Eclipse Public License - Version 1.0</h2>
+<p>
+THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 
+PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF 
+THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+</p>
+
+<h3>1. DEFINITIONS</h3>
+<p>
+"Contribution" means:
+</p><p>
+a) in the case of the initial Contributor, the initial code and documentation 
+distributed under this Agreement, and
+</p><p>
+b) in the case of each subsequent Contributor:
+</p><p>
+i) changes to the Program, and
+</p><p>
+ii) additions to the Program;
+</p><p>
+where such changes and/or additions to the Program originate from and are 
+distributed by that particular Contributor. A Contribution 'originates' from a 
+Contributor if it was added to the Program by such Contributor itself or anyone 
+acting on such Contributor's behalf. Contributions do not include additions to 
+the Program which: (i) are separate modules of software distributed in conjunction 
+with the Program under their own license agreement, and (ii) are not derivative 
+works of the Program.
+</p><p>
+"Contributor" means any person or entity that distributes the Program.
+</p><p>
+"Licensed Patents " mean patent claims licensable by a Contributor which are 
+necessarily infringed by the use or sale of its Contribution alone or when combined 
+with the Program.
+</p><p>
+"Program" means the Contributions distributed in accordance with this Agreement.
+</p><p>
+"Recipient" means anyone who receives the Program under this Agreement, 
+including all Contributors.
+</p>
+
+<h3>2. GRANT OF RIGHTS</h3>
+<p>
+a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient 
+a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare 
+derivative works of, publicly display, publicly perform, distribute and sublicense the 
+Contribution of such Contributor, if any, and such derivative works, in source code 
+and object code form.
+</p><p>
+b) Subject to the terms of this Agreement, each Contributor hereby grants 
+Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed 
+Patents to make, use, sell, offer to sell, import and otherwise transfer the 
+Contribution of such Contributor, if any, in source code and object code form. 
+This patent license shall apply to the combination of the Contribution and the 
+Program if, at the time the Contribution is added by the Contributor, such addition 
+of the Contribution causes such combination to be covered by the Licensed 
+Patents. The patent license shall not apply to any other combinations which 
+include the Contribution. No hardware per se is licensed hereunder.
+</p><p>
+c) Recipient understands that although each Contributor grants the licenses to 
+its Contributions set forth herein, no assurances are provided by any Contributor 
+that the Program does not infringe the patent or other intellectual property 
+rights of any other entity. Each Contributor disclaims any liability to Recipient 
+for claims brought by any other entity based on infringement of intellectual 
+property rights or otherwise. As a condition to exercising the rights and licenses 
+granted hereunder, each Recipient hereby assumes sole responsibility to secure 
+any other intellectual property rights needed, if any. For example, if a third party 
+patent license is required to allow Recipient to distribute the Program, it is 
+Recipient's responsibility to acquire that license before distributing the Program.
+</p><p>
+d) Each Contributor represents that to its knowledge it has sufficient copyright 
+rights in its Contribution, if any, to grant the copyright license set forth in this 
+Agreement.
+</p>
+
+<h3>3. REQUIREMENTS</h3>
+<p>
+A Contributor may choose to distribute the Program in object code form 
+under its own license agreement, provided that:
+</p><p>
+a) it complies with the terms and conditions of this Agreement; and
+</p><p>
+b) its license agreement:
+</p><p>
+i) effectively disclaims on behalf of all Contributors all warranties and conditions, 
+express and implied, including warranties or conditions of title and 
+non-infringement, and implied warranties or conditions of merchantability and 
+fitness for a particular purpose;
+</p><p>
+ii) effectively excludes on behalf of all Contributors all liability for damages, 
+including direct, indirect, special, incidental and consequential damages, 
+such as lost profits;
+</p><p>
+iii) states that any provisions which differ from this Agreement are offered by 
+that Contributor alone and not by any other party; and
+</p><p>
+iv) states that source code for the Program is available from such Contributor, 
+and informs licensees how to obtain it in a reasonable manner on or through 
+a medium customarily used for software exchange.
+</p><p>
+When the Program is made available in source code form:
+</p><p>
+a) it must be made available under this Agreement; and
+</p><p>
+b) a copy of this Agreement must be included with each copy of the Program.
+</p><p>
+Contributors may not remove or alter any copyright notices contained within 
+the Program.
+</p><p>
+Each Contributor must identify itself as the originator of its Contribution, 
+if any, in a manner that reasonably allows subsequent Recipients to identify 
+the originator of the Contribution.
+</p>
+
+<h3>4. COMMERCIAL DISTRIBUTION</h3>
+<p>
+Commercial distributors of software may accept certain responsibilities with 
+respect to end users, business partners and the like. While this license is 
+intended to facilitate the commercial use of the Program, the Contributor 
+who includes the Program in a commercial product offering should do so 
+in a manner which does not create potential liability for other Contributors. 
+Therefore, if a Contributor includes the Program in a commercial product 
+offering, such Contributor ("Commercial Contributor") hereby agrees to 
+defend and indemnify every other Contributor ("Indemnified Contributor") 
+against any losses, damages and costs (collectively "Losses") arising from 
+claims, lawsuits and other legal actions brought by a third party against the 
+Indemnified Contributor to the extent caused by the acts or omissions of 
+such Commercial Contributor in connection with its distribution of the 
+Program in a commercial product offering. The obligations in this section 
+do not apply to any claims or Losses relating to any actual or alleged 
+intellectual property infringement. In order to qualify, an Indemnified 
+Contributor must: a) promptly notify the Commercial Contributor in writing 
+of such claim, and b) allow the Commercial Contributor to control, and 
+cooperate with the Commercial Contributor in, the defense and any related 
+settlement negotiations. The Indemnified Contributor may participate in 
+any such claim at its own expense.
+</p><p>
+For example, a Contributor might include the Program in a commercial 
+product offering, Product X. That Contributor is then a Commercial 
+Contributor. If that Commercial Contributor then makes performance 
+claims, or offers warranties related to Product X, those performance 
+claims and warranties are such Commercial Contributor's responsibility 
+alone. Under this section, the Commercial Contributor would have to 
+defend claims against the other Contributors related to those performance 
+claims and warranties, and if a court requires any other Contributor to pay 
+any damages as a result, the Commercial Contributor must pay those damages.
+</p>
+
+<h3>5. NO WARRANTY</h3>
+<p>
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM 
+IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR 
+CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, 
+WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, 
+NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR 
+PURPOSE. Each Recipient is solely responsible for determining the 
+appropriateness of using and distributing the Program and assumes all 
+risks associated with its exercise of rights under this Agreement, including 
+but not limited to the risks and costs of program errors, compliance with 
+applicable laws, damage to or loss of data, programs or equipment, and 
+unavailability or interruption of operations.
+</p>
+
+<h3>6. DISCLAIMER OF LIABILITY</h3>
+<p>
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER 
+RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR 
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST 
+PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 
+USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY 
+RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 
+OF SUCH DAMAGES.
+</p>
+
+<h3>7. GENERAL</h3>
+<p>
+If any provision of this Agreement is invalid or unenforceable under applicable 
+law, it shall not affect the validity or enforceability of the remainder of the 
+terms of this Agreement, and without further action by the parties hereto, 
+such provision shall be reformed to the minimum extent necessary to make 
+such provision valid and enforceable.
+</p><p>
+If Recipient institutes patent litigation against any entity (including a cross-claim 
+or counterclaim in a lawsuit) alleging that the Program itself (excluding 
+combinations of the Program with other software or hardware) infringes such 
+Recipient's patent(s), then such Recipient's rights granted under Section 
+2(b) shall terminate as of the date such litigation is filed.
+</p><p>
+All Recipient's rights under this Agreement shall terminate if it fails to comply 
+with any of the material terms or conditions of this Agreement and does 
+not cure such failure in a reasonable period of time after becoming aware 
+of such noncompliance. If all Recipient's rights under this Agreement 
+terminate, Recipient agrees to cease use and distribution of the Program 
+as soon as reasonably practicable. However, Recipient's obligations under 
+this Agreement and any licenses granted by Recipient relating to the 
+Program shall continue and survive.
+</p><p>
+Everyone is permitted to copy and distribute copies of this Agreement, but 
+in order to avoid inconsistency the Agreement is copyrighted and may only 
+be modified in the following manner. The Agreement Steward reserves the 
+right to publish new versions (including revisions) of this Agreement from 
+time to time. No one other than the Agreement Steward has the right to 
+modify this Agreement. The Eclipse Foundation is the initial Agreement 
+Steward. The Eclipse Foundation may assign the responsibility to serve as 
+the Agreement Steward to a suitable separate entity. Each new version of 
+the Agreement will be given a distinguishing version number. The Program 
+(including Contributions) may always be distributed subject to the version 
+of the Agreement under which it was received. In addition, after a new 
+version of the Agreement is published, Contributor may elect to distribute 
+the Program (including its Contributions) under the new version. Except as 
+expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights 
+or licenses to the intellectual property of any Contributor under this 
+Agreement, whether expressly, by implication, estoppel or otherwise. 
+All rights in the Program not expressly granted under this Agreement are 
+reserved.
+</p><p>
+This Agreement is governed by the laws of <em class="u">Switzerland</em>
+and the intellectual property laws of <em class="u">Switzerland</em>. 
+No party to this Agreement will bring a legal action under this Agreement more 
+than one year after the cause of action arose. Each party waives its rights 
+to a jury trial in any resulting litigation. 
+</p>
+
+<!-- [close] { --></div></td></tr></table><!-- } --><!-- analytics --></body></html>
+
diff --git a/src/com/google/gwtorm/GWTORM.gwt.xml b/src/com/google/gwtorm/GWTORM.gwt.xml
new file mode 100644
index 0000000..358174e
--- /dev/null
+++ b/src/com/google/gwtorm/GWTORM.gwt.xml
@@ -0,0 +1,22 @@
+<!--
+ Copyright 2008 Google Inc.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<module>
+  <inherits name="com.google.gwt.core.Core"/>
+
+  <generate-with class="com.google.gwtorm.rebind.DataAccessGenerator">
+    <when-type-assignable class="com.google.gwtorm.client.DataAccess" />
+  </generate-with>
+</module>
diff --git a/src/com/google/gwtorm/client/Access.java b/src/com/google/gwtorm/client/Access.java
new file mode 100644
index 0000000..ff8058e
--- /dev/null
+++ b/src/com/google/gwtorm/client/Access.java
@@ -0,0 +1,150 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.client;
+
+/**
+ * Data access interface for an entity type.
+ * <p>
+ * Applications should extend this interface for each entity type they need to
+ * access. At runtime the application extension will be automatically
+ * implemented with a generated class, providing concrete implementations for
+ * all methods.
+ * <p>
+ * Instances should be acquired through the application's extension interface of
+ * {@link Schema}.
+ * <p>
+ * Applications should implement a query method using the {@link PrimaryKey}
+ * annotation, for example:
+ * 
+ * <pre>
+ * public interface FooAccess extends Access&lt;Foo, Foo.Key&gt; {
+ *   &#064;PrimaryKey(&quot;key&quot;)
+ *   Foo byKey(Foo.Key k) throws OrmException;
+ * }
+ *</pre>
+ *<p>
+ * otherwise the primaryKey, get, update and delete operations declared by this
+ * interface will be unsupported.
+ * 
+ * @param <T> type of the entity. Any object type is suitable, so long as at
+ *        least one field uses a {@link Column} annotation.
+ * @param <K> type of the primary key of entity. If the primary key is a
+ *        primitive type then use Key directly, otherwise use a Key
+ *        implementation. Entity specific key subclasses are recommended.
+ */
+public interface Access<T extends Object, K extends Key<?>> {
+  /**
+   * Obtain the primary key of an entity instance.
+   * 
+   * @param entity the entity to get the key of; must not be null.
+   * @return the primary key. Null if this entity has no primary key declared,
+   *         or if the primary key does not implement the Key interface.
+   */
+  K primaryKey(T entity);
+
+  /**
+   * Lookup a single entity via its primary key.
+   * <p>
+   * This method is only implemented if the entity's primary key is defined to
+   * be an implementation of the {@link Key} interface. Otherwise the method
+   * throws {@link UnsupportedOperationException}.
+   * 
+   * @param key the primary key instance; must not be null.
+   * @return the entity; null if no entity has this key.
+   * @throws OrmException the data lookup failed.
+   * @throws UnsupportedOperationException the key type doesn't implement Key.
+   */
+  T get(K key) throws OrmException;
+
+  /**
+   * Lookup multiple entities via their primary key.
+   * <p>
+   * This method is only implemented if the entity's primary key is defined to
+   * be an implementation of the {@link Key} interface. Otherwise the method
+   * throws {@link UnsupportedOperationException}.
+   * <p>
+   * This method is a batch form of {@link #get(Key)} and may be optimized to
+   * reduce round-trips to the data store.
+   * 
+   * @param keys collection of zero or more keys to perform lookup with.
+   * @return collection of all matching entities; this may be a smaller result
+   *         than the keys supplied if one or more of the keys does not match an
+   *         existing entity.
+   * @throws OrmException the data lookup failed.
+   * @throws UnsupportedOperationException the key type doesn't implement Key.
+   */
+  ResultSet<T> get(Iterable<K> keys) throws OrmException;
+
+  /**
+   * Immediately insert new entities into the data store.
+   * 
+   * @param instances the instances to insert. The iteration occurs only once.
+   * @throws OrmException data insertion failed.
+   */
+  void insert(Iterable<T> instances) throws OrmException;
+
+  /**
+   * Insert new entities into the data store.
+   * 
+   * @param instances the instances to insert. The iteration occurs only once.
+   * @param txn transaction to batch the operation into. If not null the data
+   *        store changes will be delayed to {@link Transaction#commit()} is
+   *        invoked; if null the operation occurs immediately.
+   * @throws OrmException data insertion failed.
+   */
+  void insert(Iterable<T> instances, Transaction txn) throws OrmException;
+
+  /**
+   * Immediately update existing entities in the data store.
+   * 
+   * @param instances the instances to update. The iteration occurs only once.
+   * @throws OrmException data modification failed.
+   * @throws UnsupportedOperationException no PrimaryKey was declared.
+   */
+  void update(Iterable<T> instances) throws OrmException;
+
+  /**
+   * Update existing entities in the data store.
+   * 
+   * @param instances the instances to update. The iteration occurs only once.
+   * @param txn transaction to batch the operation into. If not null the data
+   *        store changes will be delayed to {@link Transaction#commit()} is
+   *        invoked; if null the operation occurs immediately.
+   * @throws OrmException data modification failed.
+   * @throws UnsupportedOperationException no PrimaryKey was declared.
+   */
+  void update(Iterable<T> instances, Transaction txn) throws OrmException;
+
+  /**
+   * Immediately delete existing entities from the data store.
+   * 
+   * @param instances the instances to delete. The iteration occurs only once.
+   * @throws OrmException data removal failed.
+   * @throws UnsupportedOperationException no PrimaryKey was declared.
+   */
+  void delete(Iterable<T> instances) throws OrmException;
+
+  /**
+   * Delete existing entities from the data store.
+   * 
+   * @param instances the instances to delete. The iteration occurs only once.
+   * @param txn transaction to batch the operation into. If not null the data
+   *        store changes will be delayed to {@link Transaction#commit()} is
+   *        invoked; if null the operation occurs immediately.
+   * @throws OrmException data removal failed.
+   * @throws UnsupportedOperationException no PrimaryKey was declared.
+   */
+  void delete(Iterable<T> instances, Transaction txn) throws OrmException;
+}
diff --git a/src/com/google/gwtorm/client/Column.java b/src/com/google/gwtorm/client/Column.java
new file mode 100644
index 0000000..a46f26c
--- /dev/null
+++ b/src/com/google/gwtorm/client/Column.java
@@ -0,0 +1,47 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.client;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation marking an entity field for persistence in the data store.
+ * <p>
+ * Fields marked with <code>Column</code> must not be final and must not be
+ * private. Fields which might be accessed cross-packages (such as those
+ * declared in a common Key type like {@link StringKey}) must be declared with
+ * public access so generated code can access them directly.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface Column {
+  /**
+   * @return name of the column in the data store. Defaults to the field name.
+   */
+  String name() default "";
+
+  /**
+   * @return maximum length (in characters). Only valid for String.
+   */
+  int length() default 0;
+
+  /**
+   * @return is a value required. Defaults to true (NOT NULL).
+   */
+  boolean notNull() default true;
+}
diff --git a/src/com/google/gwtorm/client/IntKey.java b/src/com/google/gwtorm/client/IntKey.java
new file mode 100644
index 0000000..7dcfbf7
--- /dev/null
+++ b/src/com/google/gwtorm/client/IntKey.java
@@ -0,0 +1,85 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.client;
+
+import java.io.Serializable;
+
+/**
+ * Abstract key type using a single integer value.
+ * <p>
+ * Applications should subclass this type to create their own entity-specific
+ * key classes.
+ * 
+ * @param <P> the parent key type. Use {@link Key} if no parent key is needed.
+ */
+public abstract class IntKey<P extends Key<?>> implements Key<P>, Serializable {
+  /**
+   * @return id of the entity instance.
+   */
+  public abstract int get();
+
+  /**
+   * @return the parent key instance; null if this is a root level key.
+   */
+  public P getParentKey() {
+    return null;
+  }
+
+  @Override
+  public int hashCode() {
+    int hc = get();
+    if (getParentKey() != null) {
+      hc *= 31;
+      hc += getParentKey().hashCode();
+    }
+    return hc;
+  }
+
+  @Override
+  public boolean equals(final Object b) {
+    if (b == null || b.getClass() != getClass()) {
+      return false;
+    }
+
+    final IntKey<P> q = cast(b);
+    if (get() == q.get()) {
+      if (getParentKey() != null) {
+        return getParentKey().equals(q.getParentKey());
+      }
+      return true;
+    }
+
+    return false;
+  }
+
+  @Override
+  public String toString() {
+    final StringBuffer r = new StringBuffer();
+    r.append(getClass().getName());
+    r.append('[');
+    if (getParentKey() != null) {
+      r.append(getParentKey().toString());
+      r.append(", ");
+    }
+    r.append(get());
+    r.append(']');
+    return r.toString();
+  }
+
+  @SuppressWarnings("unchecked")
+  private static <A extends Key<?>> IntKey<A> cast(final Object b) {
+    return (IntKey<A>) b;
+  }
+}
diff --git a/src/com/google/gwtorm/client/Key.java b/src/com/google/gwtorm/client/Key.java
new file mode 100644
index 0000000..0505075
--- /dev/null
+++ b/src/com/google/gwtorm/client/Key.java
@@ -0,0 +1,33 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.client;
+
+/**
+ * Generic type for an entity key.
+ * <p>
+ * Although not required, entities should make their primary key type implement
+ * this interface, permitting traversal up through the containment hierarchy of
+ * the entity keys.
+ * 
+ * @param <P> type of the parent key. If no parent, use {@link Key} itself.
+ */
+public interface Key<P extends Key<?>> {
+  /**
+   * Get the parent key instance.
+   * 
+   * @return the parent key; null if this entity key is a root-level key.
+   */
+  public P getParentKey();
+}
diff --git a/src/com/google/gwtorm/client/LongKey.java b/src/com/google/gwtorm/client/LongKey.java
new file mode 100644
index 0000000..2fc5d6b
--- /dev/null
+++ b/src/com/google/gwtorm/client/LongKey.java
@@ -0,0 +1,85 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.client;
+
+import java.io.Serializable;
+
+/**
+ * Abstract key type using a single long value.
+ * <p>
+ * Applications should subclass this type to create their own entity-specific
+ * key classes.
+ * 
+ * @param <P> the parent key type. Use {@link Key} if no parent key is needed.
+ */
+public abstract class LongKey<P extends Key<?>> implements Key<P>, Serializable {
+  /**
+   * @return id of the entity instance.
+   */
+  public abstract long get();
+
+  /**
+   * @return the parent key instance; null if this is a root level key.
+   */
+  public P getParentKey() {
+    return null;
+  }
+
+  @Override
+  public int hashCode() {
+    int hc = (int) get();
+    if (getParentKey() != null) {
+      hc *= 31;
+      hc += getParentKey().hashCode();
+    }
+    return hc;
+  }
+
+  @Override
+  public boolean equals(final Object b) {
+    if (b == null || b.getClass() != getClass()) {
+      return false;
+    }
+
+    final LongKey<P> q = cast(b);
+    if (get() == q.get()) {
+      if (getParentKey() != null) {
+        return getParentKey().equals(q.getParentKey());
+      }
+      return true;
+    }
+
+    return false;
+  }
+
+  @Override
+  public String toString() {
+    final StringBuffer r = new StringBuffer();
+    r.append(getClass().getName());
+    r.append('[');
+    if (getParentKey() != null) {
+      r.append(getParentKey().toString());
+      r.append(", ");
+    }
+    r.append(get());
+    r.append(']');
+    return r.toString();
+  }
+
+  @SuppressWarnings("unchecked")
+  private static <A extends Key<?>> LongKey<A> cast(final Object b) {
+    return (LongKey<A>) b;
+  }
+}
diff --git a/src/com/google/gwtorm/client/OrmException.java b/src/com/google/gwtorm/client/OrmException.java
new file mode 100644
index 0000000..72cb144
--- /dev/null
+++ b/src/com/google/gwtorm/client/OrmException.java
@@ -0,0 +1,28 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.client;
+
+/**
+ * Any data store read or write error.
+ */
+public class OrmException extends Exception {
+  public OrmException(final String message) {
+    super(message);
+  }
+
+  public OrmException(final String message, final Throwable why) {
+    super(message, why);
+  }
+}
diff --git a/src/com/google/gwtorm/client/PrimaryKey.java b/src/com/google/gwtorm/client/PrimaryKey.java
new file mode 100644
index 0000000..b1308f4
--- /dev/null
+++ b/src/com/google/gwtorm/client/PrimaryKey.java
@@ -0,0 +1,34 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.client;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation marking a query function in a {@link Access} interface as the key.
+ * 
+ * @see Access
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface PrimaryKey {
+  /**
+   * @return name of the field in the entity which contains the primary key.
+   */
+  String value();
+}
diff --git a/src/com/google/gwtorm/client/Query.java b/src/com/google/gwtorm/client/Query.java
new file mode 100644
index 0000000..19359f6
--- /dev/null
+++ b/src/com/google/gwtorm/client/Query.java
@@ -0,0 +1,56 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.client;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation marking a method in an {@link Access} interface as a query.
+ * <p>
+ * Query methods must return a parameterized {@link ResultSet}, for example:
+ * 
+ * <pre>
+ * public interface FooAccess extends Access&lt;Foo, Foo.Key&gt; {
+ *   &#064;Query(&quot;WHERE a=?&quot;)
+ *   ResultSet&lt;Foo&gt; find(int a) throws OrmException;
+ * }
+ *</pre>
+ *<p>
+ * Query strings must conform to the following grammar:
+ * 
+ * <pre>
+ * [WHERE &lt;condition&gt; [AND &lt;condition&gt; ...]]
+ * [ORDER BY &lt;property&gt; [ASC | DESC] [, &lt;property&gt; [ASC | DESC] ...]]
+ * [LIMIT { &lt;count&gt; | ? }]
+ * 
+ * &lt;condition&gt; := &lt;property&gt; { &lt; | &lt;= | &gt; | &gt;= | = | != } &lt;value&gt;
+ * &lt;value&gt; := { ? | true | false | &lt;int&gt; | &lt;string&gt; }
+ * </pre>
+ * <p>
+ * Method parameters are bound in order to the placeholders (?) declared in the
+ * query conditions. The type of the limit placeholder parameter (if used in the
+ * query) must be <code>int</code>.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface Query {
+  /**
+   * @return the query clause. Defaults to "", matching all entities, no order.
+   */
+  String value() default "";
+}
diff --git a/src/com/google/gwtorm/client/Relation.java b/src/com/google/gwtorm/client/Relation.java
new file mode 100644
index 0000000..a43a033
--- /dev/null
+++ b/src/com/google/gwtorm/client/Relation.java
@@ -0,0 +1,48 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.client;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation marking a method in a {@link Schema} interface as entity access.
+ * <p>
+ * Access methods must return an interface extending {@link Access}, for
+ * example:
+ * 
+ * <pre>
+ * public interface FooAccess extends Access&lt;Foo, Foo.Key&gt; {
+ * }
+ * 
+ * public interface BarSchema extends Schema {
+ *   &#064;Relation
+ *   FooAccess foos();
+ * }
+ * </pre>
+ * <p>
+ * The table name within the data store will be derived from the relation
+ * annotation, or the method name.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface Relation {
+  /**
+   * @return the name of the data store table. Defaults to the method name.
+   */
+  String name() default "";
+}
diff --git a/src/com/google/gwtorm/client/ResultSet.java b/src/com/google/gwtorm/client/ResultSet.java
new file mode 100644
index 0000000..c8c392b
--- /dev/null
+++ b/src/com/google/gwtorm/client/ResultSet.java
@@ -0,0 +1,52 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.client;
+
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Result from any data store query function.
+ * 
+ * @param <T> type of entity being returned by the query.
+ */
+public interface ResultSet<T> extends Iterable<T> {
+  /**
+   * Obtain an iterator to loop through the results.
+   * <p>
+   * The iterator can be obtained only once. When the iterator completes (
+   * <code>hasNext()</code> returns false) {@link #close()} will be
+   * automatically called.
+   */
+  Iterator<T> iterator();
+
+  /**
+   * Materialize all results as a single list.
+   * <p>
+   * Prior to returning {@link #close()} is invoked. This method must not be
+   * combined with {@link #iterator()} on the same instance.
+   * 
+   * @return list of the complete results.
+   */
+  List<T> toList();
+
+  /**
+   * Close the result, discarding any further results.
+   * <p>
+   * This method may be invoked more than once. Its main use is to stop
+   * obtaining results before the iterator has finished.
+   */
+  void close();
+}
diff --git a/src/com/google/gwtorm/client/Schema.java b/src/com/google/gwtorm/client/Schema.java
new file mode 100644
index 0000000..b28da3f
--- /dev/null
+++ b/src/com/google/gwtorm/client/Schema.java
@@ -0,0 +1,83 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.client;
+
+/**
+ * Application database definition and top-level schema access.
+ * <p>
+ * Applications should extend this interface and declare relation methods for
+ * each entity/table used. Relation methods must be marked with the
+ * {@link Relation} annotation and return an interface extending {@link Access}.
+ * At runtime the application extension of Schema will be automatically
+ * implemented with a generated class, providing implementations of the Access
+ * extensions from each of the declared relation methods.
+ * <p>
+ * Instances of a schema should be obtained through the
+ * {@link com.google.gwtorm.jdbc.Database} class on a pure-JDBC implementation
+ * and through <code>GWT.create()</code> on the GWT client side.
+ * <p>
+ * In the JDBC implementation each Schema instance wraps around a single JDBC
+ * Connection object. Therefore a Schema instance has a 1:1 relationship with an
+ * active database handle.
+ * <p>
+ * A Schema instance (as well as its returned Access instances) is not thread
+ * safe. Applications must provide their own synchronization, or ensure that at
+ * most 1 thread access a Schema instance (or any returned Access instance) at a
+ * time. The safest mapping is 1 schema instance per thread, never shared.
+ * <p>
+ * For example the OurDb schema creates two tables (identical structure) named
+ * <code>someFoos</code> and <code>otherFoos</code>:
+ * 
+ * <pre>
+ * public interface FooAccess extends Access&lt;Foo, Foo.Key&gt; {
+ *   &#064;PrimaryKey(&quot;key&quot;)
+ *   Foo byKey(Foo.Key k) throws OrmException;
+ * }
+ * 
+ * public interface OurDb extends Schema {
+ *   &#064;Relation
+ *   FooAccess someFoos();
+ * 
+ *   &#064;Relation
+ *   FooAccess otherFoos();
+ * }
+ * </pre>
+ */
+public interface Schema {
+  /**
+   * Automatically create the database tables.
+   * 
+   * @throws OrmException tables already exist or create permission is denied.
+   */
+  void createSchema() throws OrmException;
+
+  /**
+   * Begin a new transaction.
+   * <p>
+   * Only one transaction can be in-flight at a time on any given Schema
+   * instance. Applications must commit or rollback a previously created
+   * transaction before beginning another transaction on the same Schema.
+   * 
+   * @return the new transaction.
+   * @throws OrmException the schema has been closed or another transaction has
+   *         already been begun on this schema instance.
+   */
+  Transaction beginTransaction() throws OrmException;
+
+  /**
+   * Close the schema and release all resources.
+   */
+  void close();
+}
diff --git a/src/com/google/gwtorm/client/Sequence.java b/src/com/google/gwtorm/client/Sequence.java
new file mode 100644
index 0000000..c70cc90
--- /dev/null
+++ b/src/com/google/gwtorm/client/Sequence.java
@@ -0,0 +1,61 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.client;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation marking a method in {@link Schema} interface as number generator.
+ * <p>
+ * Sequence methods must return a primitive <code>int</code> or
+ * <code>long</code> type.
+ * 
+ * <pre>
+ * public interface BarSchema extends Schema {
+ *   &#064;Sequence
+ *   int nextId();
+ * }
+ * </pre>
+ * <p>
+ * The sequence name will be taken from the method name, after removing the
+ * optional prefix "next".
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface Sequence {
+  /**
+   * @return the name of the sequence. Defaults to the method name.
+   */
+  String name() default "";
+
+  /**
+   * @return the initial value of the sequence. Defaults to 1, or whatever the
+   *         database dialect defaults to if the sequence starting value is not
+   *         supplied in the sequence declaration.
+   */
+  long startsWith() default 0;
+
+  /**
+   * @return maximum number of values to cache in memory from the sequence.
+   *         Defaults to -1, indicating a default caching level should be
+   *         determined by the database. Cached values may be lost (never
+   *         returned by the sequence, creating gaps) if the application or the
+   *         database is shutdown and restarted.
+   */
+  int cache() default -1;
+}
diff --git a/src/com/google/gwtorm/client/StringKey.java b/src/com/google/gwtorm/client/StringKey.java
new file mode 100644
index 0000000..427c8ad
--- /dev/null
+++ b/src/com/google/gwtorm/client/StringKey.java
@@ -0,0 +1,86 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.client;
+
+import java.io.Serializable;
+
+/**
+ * Abstract key type using a single string value.
+ * <p>
+ * Applications should subclass this type to create their own entity-specific
+ * key classes.
+ * 
+ * @param <P> the parent key type. Use {@link Key} if no parent key is needed.
+ */
+public abstract class StringKey<P extends Key<?>> implements Key<P>,
+    Serializable {
+  /**
+   * @return name of the entity instance.
+   */
+  public abstract String get();
+
+  /**
+   * @return the parent key instance; null if this is a root level key.
+   */
+  public P getParentKey() {
+    return null;
+  }
+
+  @Override
+  public int hashCode() {
+    int hc = get() != null ? get().hashCode() : 0;
+    if (getParentKey() != null) {
+      hc *= 31;
+      hc += getParentKey().hashCode();
+    }
+    return hc;
+  }
+
+  @Override
+  public boolean equals(final Object b) {
+    if (b == null || get() == null || b.getClass() != getClass()) {
+      return false;
+    }
+
+    final StringKey<P> q = cast(b);
+    if (get().equals(q.get())) {
+      if (getParentKey() != null) {
+        return getParentKey().equals(q.getParentKey());
+      }
+      return true;
+    }
+
+    return false;
+  }
+
+  @Override
+  public String toString() {
+    final StringBuffer r = new StringBuffer();
+    r.append(getClass().getName());
+    r.append('[');
+    if (getParentKey() != null) {
+      r.append(getParentKey().toString());
+      r.append(", ");
+    }
+    r.append(get());
+    r.append(']');
+    return r.toString();
+  }
+
+  @SuppressWarnings("unchecked")
+  private static <A extends Key<?>> StringKey<A> cast(final Object b) {
+    return (StringKey<A>) b;
+  }
+}
diff --git a/src/com/google/gwtorm/client/Transaction.java b/src/com/google/gwtorm/client/Transaction.java
new file mode 100644
index 0000000..8f2963b
--- /dev/null
+++ b/src/com/google/gwtorm/client/Transaction.java
@@ -0,0 +1,49 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.client;
+
+/**
+ * An active transaction running on a {@link Schema}.
+ * <p>
+ * Applications must invoke {@link #commit()} to finish a transaction.
+ * <p>
+ * Use method on one or more {@link Access} instances to schedule changes into
+ * an open transaction:
+ * <ul>
+ * <li>{@link Access#insert(Iterable, Transaction)}</li>
+ * <li>{@link Access#update(Iterable, Transaction)}</li>
+ * <li>{@link Access#delete(Iterable, Transaction)}</li>
+ * <ul>
+ * 
+ * @see Schema#beginTransaction()
+ */
+public interface Transaction {
+  /**
+   * Commit this transaction, finishing all actions.
+   * 
+   * @throws OrmException data store refused/rejected one or more actions.
+   */
+  void commit() throws OrmException;
+
+  /**
+   * Rollback (abort) this transaction, performing none of the actions.
+   * <p>
+   * This method has no affect if the transaction has not made any changes.
+   * 
+   * @throws OrmException data store couldn't undo the transaction, as it is
+   *         already committed.
+   */
+  void rollback() throws OrmException;
+}
diff --git a/src/com/google/gwtorm/client/impl/AbstractAccess.java b/src/com/google/gwtorm/client/impl/AbstractAccess.java
new file mode 100644
index 0000000..237feef
--- /dev/null
+++ b/src/com/google/gwtorm/client/impl/AbstractAccess.java
@@ -0,0 +1,90 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.client.impl;
+
+import com.google.gwtorm.client.Access;
+import com.google.gwtorm.client.Key;
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.ResultSet;
+import com.google.gwtorm.client.Transaction;
+
+import java.util.ArrayList;
+
+public abstract class AbstractAccess<E, K extends Key<?>, T extends AbstractTransaction>
+    implements Access<E, K> {
+  public ResultSet<E> get(final Iterable<K> keys) throws OrmException {
+    final ArrayList<E> r = new ArrayList<E>();
+    for (final K key : keys) {
+      final E o = get(key);
+      if (o != null) {
+        r.add(o);
+      }
+    }
+    return new ListResultSet<E>(r);
+  }
+
+  public final void insert(final Iterable<E> instances) throws OrmException {
+    doInsert(instances, null);
+  }
+
+  public final void insert(final Iterable<E> instances, final Transaction txn)
+      throws OrmException {
+    if (txn != null) {
+      cast(txn).queueInsert(this, instances);
+    } else {
+      insert(instances);
+    }
+  }
+
+  public final void update(final Iterable<E> instances) throws OrmException {
+    doUpdate(instances, null);
+  }
+
+  public final void update(final Iterable<E> instances, final Transaction txn)
+      throws OrmException {
+    if (txn != null) {
+      cast(txn).queueUpdate(this, instances);
+    } else {
+      update(instances);
+    }
+  }
+
+  public final void delete(final Iterable<E> instances) throws OrmException {
+    doDelete(instances, null);
+  }
+
+  public final void delete(final Iterable<E> instances, final Transaction txn)
+      throws OrmException {
+    if (txn != null) {
+      cast(txn).queueDelete(this, instances);
+    } else {
+      delete(instances);
+    }
+  }
+
+  protected abstract void doInsert(Iterable<E> instances, T txn)
+      throws OrmException;
+
+  protected abstract void doUpdate(Iterable<E> instances, T txn)
+      throws OrmException;
+
+  protected abstract void doDelete(Iterable<E> instances, T txn)
+      throws OrmException;
+
+  @SuppressWarnings("unchecked")
+  private T cast(final Transaction txn) {
+    return ((T) txn);
+  }
+}
diff --git a/src/com/google/gwtorm/client/impl/AbstractTransaction.java b/src/com/google/gwtorm/client/impl/AbstractTransaction.java
new file mode 100644
index 0000000..a424f8f
--- /dev/null
+++ b/src/com/google/gwtorm/client/impl/AbstractTransaction.java
@@ -0,0 +1,124 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.client.impl;
+
+import com.google.gwtorm.client.Key;
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.Transaction;
+
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+public abstract class AbstractTransaction implements Transaction {
+  private static LinkedHashMap<Object, Action<?, Key<?>, AbstractTransaction>> newMap() {
+    return new LinkedHashMap<Object, Action<?, Key<?>, AbstractTransaction>>();
+  }
+
+  protected final Map<Object, Action<?, Key<?>, AbstractTransaction>> pendingInsert;
+  protected final Map<Object, Action<?, Key<?>, AbstractTransaction>> pendingUpdate;
+  protected final Map<Object, Action<?, Key<?>, AbstractTransaction>> pendingDelete;
+
+  protected AbstractTransaction() {
+    pendingInsert = newMap();
+    pendingUpdate = newMap();
+    pendingDelete = newMap();
+  }
+
+  public void commit() throws OrmException {
+    for (Action<?, Key<?>, AbstractTransaction> a : pendingDelete.values()) {
+      a.doDelete(this);
+    }
+    for (Action<?, Key<?>, AbstractTransaction> a : pendingInsert.values()) {
+      a.doInsert(this);
+    }
+    for (Action<?, Key<?>, AbstractTransaction> a : pendingUpdate.values()) {
+      a.doUpdate(this);
+    }
+  }
+
+  <E, K extends Key<?>, T extends AbstractTransaction> void queueInsert(
+      final AbstractAccess<E, ?, T> access, final Iterable<E> list) {
+    queue(pendingInsert, access, list);
+  }
+
+  <E, K extends Key<?>, T extends AbstractTransaction> void queueUpdate(
+      final AbstractAccess<E, ?, T> access, final Iterable<E> list) {
+    queue(pendingUpdate, access, list);
+  }
+
+  <E, K extends Key<?>, T extends AbstractTransaction> void queueDelete(
+      final AbstractAccess<E, ?, T> access, final Iterable<E> list) {
+    queue(pendingDelete, access, list);
+  }
+
+  private static <E, K extends Key<?>, T extends AbstractTransaction> void queue(
+      final Map<Object, Action<?, Key<?>, AbstractTransaction>> queue,
+      final AbstractAccess<E, K, T> access, final Iterable<E> list) {
+    Action<E, K, T> c = get(queue, access);
+    if (c == null) {
+      c = new Action<E, K, T>(access);
+      put(queue, c);
+    }
+    c.addAll(list);
+  }
+
+  @SuppressWarnings("unchecked")
+  private static <E, K extends Key<?>, T extends AbstractTransaction> Action<E, K, T> get(
+      final Map<Object, Action<?, Key<?>, AbstractTransaction>> q,
+      final AbstractAccess<E, K, T> access) {
+    return (Action<E, K, T>) q.get(access);
+  }
+
+  @SuppressWarnings("unchecked")
+  private static <E, K extends Key<?>, T extends AbstractTransaction> void put(
+      final Map queue, Action<E, K, T> c) {
+    // This silly little method was needed to defeat the Java compiler's
+    // generic type checking. Somehow we got lost in the anonymous types
+    // from all the ? in our Map definition and the compiler just won't let
+    // us do a put into the map.
+    //
+    queue.put(c.access, c);
+  }
+
+  private static class Action<E, K extends Key<?>, T extends AbstractTransaction> {
+    private final AbstractAccess<E, K, T> access;
+    private final Set<E> instances;
+
+    Action(final AbstractAccess<E, K, T> a) {
+      access = a;
+      instances = new LinkedHashSet<E>();
+    }
+
+    void addAll(final Iterable<E> list) {
+      for (final E o : list) {
+        instances.add(o);
+      }
+    }
+
+    void doInsert(final T t) throws OrmException {
+      access.doInsert(instances, t);
+    }
+
+    void doUpdate(final T t) throws OrmException {
+      access.doUpdate(instances, t);
+    }
+
+    void doDelete(final T t) throws OrmException {
+      access.doDelete(instances, t);
+    }
+  }
+}
diff --git a/src/com/google/gwtorm/client/impl/ListResultSet.java b/src/com/google/gwtorm/client/impl/ListResultSet.java
new file mode 100644
index 0000000..6f48d28
--- /dev/null
+++ b/src/com/google/gwtorm/client/impl/ListResultSet.java
@@ -0,0 +1,45 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.client.impl;
+
+import com.google.gwtorm.client.ResultSet;
+
+import java.util.Iterator;
+import java.util.List;
+
+public class ListResultSet<T> implements ResultSet<T> {
+  private List<T> items;
+
+  public ListResultSet(final List<T> r) {
+    items = r;
+  }
+
+  public Iterator<T> iterator() {
+    return toList().iterator();
+  }
+
+  public List<T> toList() {
+    final List<T> r = items;
+    if (r == null) {
+      throw new IllegalStateException("Results already obtained");
+    }
+    items = null;
+    return r;
+  }
+
+  public void close() {
+    items = null;
+  }
+}
diff --git a/src/com/google/gwtorm/jdbc/AbstractSchemaFactory.java b/src/com/google/gwtorm/jdbc/AbstractSchemaFactory.java
new file mode 100644
index 0000000..d087a72
--- /dev/null
+++ b/src/com/google/gwtorm/jdbc/AbstractSchemaFactory.java
@@ -0,0 +1,39 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.jdbc;
+
+import com.google.gwtorm.client.Schema;
+
+import java.sql.Connection;
+
+/**
+ * Internal interface to quickly create Schema instances.
+ * <p>
+ * Applications should not use this interface. It is automatically implemented
+ * at runtime to provide fast construction for new Schema instances within
+ * {@link Database#open()}.
+ * 
+ * @param <T> type of the application schema.
+ */
+public abstract class AbstractSchemaFactory<T extends Schema> {
+  /**
+   * Create a new schema instance.
+   * 
+   * @param db the database instance which created the connection.
+   * @param c the JDBC connection the instance will talk to the database on.
+   * @return the new schema instance, wrapping the connection.
+   */
+  public abstract T create(Database<T> db, Connection c);
+}
diff --git a/src/com/google/gwtorm/jdbc/Database.java b/src/com/google/gwtorm/jdbc/Database.java
new file mode 100644
index 0000000..d45566d
--- /dev/null
+++ b/src/com/google/gwtorm/jdbc/Database.java
@@ -0,0 +1,209 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.jdbc;
+
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.Schema;
+import com.google.gwtorm.jdbc.gen.GeneratedClassLoader;
+import com.google.gwtorm.jdbc.gen.SchemaFactoryGen;
+import com.google.gwtorm.jdbc.gen.SchemaGen;
+import com.google.gwtorm.schema.SchemaModel;
+import com.google.gwtorm.schema.java.JavaSchemaModel;
+import com.google.gwtorm.schema.sql.DialectH2;
+import com.google.gwtorm.schema.sql.DialectPostgreSQL;
+import com.google.gwtorm.schema.sql.SqlDialect;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Properties;
+import java.util.WeakHashMap;
+
+/**
+ * Constructor for application {@link Schema} extensions.
+ * <p>
+ * Applications should use the Database class to create instances of their
+ * Schema extension interface, and thus open and connect to the JDBC data store.
+ * <p>
+ * Creating a new Database instance is expensive, due to the type analysis and
+ * code generation performed to implement the Schema and Access interfaces.
+ * Applications should create and cache their Database instance for the live of
+ * the application.
+ * <p>
+ * Database instances are thread-safe, but returned Schema instances are not.
+ * 
+ * @param <T>
+ */
+public class Database<T extends Schema> {
+  private static final Map<Class<?>, String> schemaFactoryNames =
+      Collections.synchronizedMap(new WeakHashMap<Class<?>, String>());
+
+  private final Properties connectionInfo;
+  private final String url;
+  private final JavaSchemaModel schemaModel;
+  private final AbstractSchemaFactory<T> implFactory;
+  private final SqlDialect implDialect;
+
+  /**
+   * Create a new database interface, generating the interface implementations.
+   * <p>
+   * The JDBC properties information must define at least <code>url</code> and
+   * <code>driver</code>, but may also include driver specific properties such
+   * as <code>username</code> and <code>password</code>.
+   * 
+   * @param dbInfo JDBC connection information. The property table is copied.
+   * @param schema application extension of the Schema interface to implement.
+   * @throws OrmException the schema interface is incorrectly defined, or the
+   *         driver class is not available through the current class loader.
+   */
+  public Database(final Properties dbInfo, final Class<T> schema)
+      throws OrmException {
+    connectionInfo = new Properties();
+    connectionInfo.putAll(dbInfo);
+
+    url = (String) connectionInfo.remove("url");
+    if (url == null) {
+      throw new OrmException("Required property 'url' not defined");
+    }
+
+    final String driver = (String) connectionInfo.remove("driver");
+    if (driver != null) {
+      loadDriver(driver);
+    }
+
+    final SqlDialect dialect;
+    String dialectName = (String) connectionInfo.remove("dialect");
+    if (dialectName != null) {
+      if (!dialectName.contains(".")) {
+        final String n = SqlDialect.class.getName();
+        dialectName = n.substring(0, n.lastIndexOf('.') + 1) + dialectName;
+      }
+      try {
+        dialect = (SqlDialect) Class.forName(dialectName).newInstance();
+      } catch (InstantiationException e) {
+        throw new OrmException("Dialect " + dialectName + " not available", e);
+      } catch (IllegalAccessException e) {
+        throw new OrmException("Dialect " + dialectName + " not available", e);
+      } catch (ClassNotFoundException e) {
+        throw new OrmException("Dialect " + dialectName + " not found", e);
+      }
+    } else if (url.startsWith("jdbc:postgresql:")) {
+      dialect = new DialectPostgreSQL();
+    } else if (url.startsWith("jdbc:h2:")) {
+      dialect = new DialectH2();
+    } else {
+      throw new OrmException("No dialect known for " + url);
+    }
+
+    schemaModel = new JavaSchemaModel(schema);
+    final GeneratedClassLoader loader = newLoader(schema);
+    final String cachedName = schemaFactoryNames.get(schema);
+    AbstractSchemaFactory<T> factory = null;
+    if (cachedName != null) {
+      factory = newFactory(loader, cachedName);
+    }
+    if (factory == null) {
+      final SchemaGen gen = new SchemaGen(loader, schemaModel, dialect);
+      gen.defineClass();
+      factory = new SchemaFactoryGen<T>(loader, gen).create();
+      schemaFactoryNames.put(schema, factory.getClass().getName());
+    }
+    implFactory = factory;
+    implDialect = dialect;
+  }
+
+  @SuppressWarnings("unchecked")
+  private AbstractSchemaFactory<T> newFactory(final ClassLoader cl,
+      final String name) {
+    try {
+      final Class<?> ft = Class.forName(name, true, cl);
+      return (AbstractSchemaFactory<T>) ft.newInstance();
+    } catch (InstantiationException e) {
+      return null;
+    } catch (IllegalAccessException e) {
+      return null;
+    } catch (ClassNotFoundException e) {
+      return null;
+    }
+  }
+
+  SqlDialect getDialect() {
+    return implDialect;
+  }
+
+  SchemaModel getSchemaModel() {
+    return schemaModel;
+  }
+
+  /**
+   * Open a new connection to the database and get a Schema wrapper.
+   * 
+   * @return a new JDBC connection, wrapped up in the application's Schema.
+   * @throws OrmException the connection could not be opened to the database.
+   *         The JDBC exception detail should be examined to determine the root
+   *         cause of the connection failure.
+   */
+  public T open() throws OrmException {
+    final Connection conn;
+    try {
+      conn = DriverManager.getConnection(url, connectionInfo);
+    } catch (SQLException e) {
+      throw new OrmException("Cannot open database connection", e);
+    }
+
+    try {
+      if (!conn.getAutoCommit()) {
+        conn.setAutoCommit(true);
+      }
+    } catch (SQLException e) {
+      try {
+        conn.close();
+      } catch (SQLException e2) {
+      }
+      throw new OrmException("Cannot force auto-commit on connection", e);
+    }
+
+    return implFactory.create(this, conn);
+  }
+
+  private static <T> GeneratedClassLoader newLoader(final Class<T> schema) {
+    return new GeneratedClassLoader(schema.getClassLoader());
+  }
+
+  private static synchronized void loadDriver(final String driver)
+      throws OrmException {
+    // I've seen some drivers (*cough* Informix *cough*) which won't load
+    // on multiple threads at the same time. Forcing our code to synchronize
+    // around loading the driver ensures we won't ever ask for the same driver
+    // to initialize from different threads. Of course that could still happen
+    // in other parts of the same JVM, but its quite unlikely.
+    //
+    try {
+      Class.forName(driver, true, threadCL());
+    } catch (ClassNotFoundException err) {
+      throw new OrmException("Driver class " + driver + " not available", err);
+    }
+  }
+
+  private static ClassLoader threadCL() {
+    try {
+      return Thread.currentThread().getContextClassLoader();
+    } catch (SecurityException e) {
+      return Database.class.getClassLoader();
+    }
+  }
+}
diff --git a/src/com/google/gwtorm/jdbc/JdbcAccess.java b/src/com/google/gwtorm/jdbc/JdbcAccess.java
new file mode 100644
index 0000000..c651f37
--- /dev/null
+++ b/src/com/google/gwtorm/jdbc/JdbcAccess.java
@@ -0,0 +1,206 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.jdbc;
+
+import com.google.gwtorm.client.Access;
+import com.google.gwtorm.client.Key;
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.impl.AbstractAccess;
+import com.google.gwtorm.client.impl.ListResultSet;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+
+/** Internal base class for implementations of {@link Access}. */
+public abstract class JdbcAccess<T, K extends Key<?>> extends
+    AbstractAccess<T, K, JdbcTransaction> {
+  private final JdbcSchema schema;
+
+  protected JdbcAccess(final JdbcSchema s) {
+    schema = s;
+  }
+
+  protected PreparedStatement prepareStatement(final String sql)
+      throws OrmException {
+    try {
+      return schema.getConnection().prepareStatement(sql);
+    } catch (SQLException e) {
+      throw new OrmException("Prepare failure\n" + sql, e);
+    }
+  }
+
+  protected T queryOne(final PreparedStatement ps) throws OrmException {
+    try {
+      try {
+        final ResultSet rs = ps.executeQuery();
+        try {
+          T r = null;
+          if (rs.next()) {
+            r = newEntityInstance();
+            bindOneFetch(rs, r);
+            if (rs.next()) {
+              throw new OrmException("Multiple results");
+            }
+          }
+          return r;
+        } finally {
+          rs.close();
+        }
+      } finally {
+        ps.close();
+      }
+    } catch (SQLException e) {
+      throw new OrmException("Fetch failure: " + getRelationName(), e);
+    }
+  }
+
+  protected ListResultSet<T> queryList(final PreparedStatement ps)
+      throws OrmException {
+    try {
+      try {
+        final ResultSet rs = ps.executeQuery();
+        try {
+          final ArrayList<T> r = new ArrayList<T>();
+          while (rs.next()) {
+            final T o = newEntityInstance();
+            bindOneFetch(rs, o);
+            r.add(o);
+          }
+          return new ListResultSet<T>(r);
+        } finally {
+          rs.close();
+        }
+      } finally {
+        ps.close();
+      }
+    } catch (SQLException e) {
+      throw new OrmException("Fetch failure: " + getRelationName(), e);
+    }
+  }
+
+  @Override
+  protected void doInsert(final Iterable<T> instances, final JdbcTransaction txn)
+      throws OrmException {
+    try {
+      final PreparedStatement ps;
+
+      ps = schema.getConnection().prepareStatement(getInsertOneSql());
+      try {
+        int cnt = 0;
+        for (final T o : instances) {
+          bindOneInsert(ps, o);
+          ps.addBatch();
+          cnt++;
+        }
+        execute(ps, cnt);
+      } finally {
+        ps.close();
+      }
+    } catch (SQLException e) {
+      throw new OrmException("Insert failure: " + getRelationName(), e);
+    }
+  }
+
+  @Override
+  protected void doUpdate(final Iterable<T> instances, final JdbcTransaction txn)
+      throws OrmException {
+    try {
+      final PreparedStatement ps;
+
+      ps = schema.getConnection().prepareStatement(getUpdateOneSql());
+      try {
+        int cnt = 0;
+        for (final T o : instances) {
+          bindOneUpdate(ps, o);
+          ps.addBatch();
+          cnt++;
+        }
+        execute(ps, cnt);
+      } finally {
+        ps.close();
+      }
+    } catch (SQLException e) {
+      throw new OrmException("Update failure: " + getRelationName(), e);
+    }
+  }
+
+  @Override
+  protected void doDelete(final Iterable<T> instances, final JdbcTransaction txn)
+      throws OrmException {
+    try {
+      final PreparedStatement ps;
+
+      ps = schema.getConnection().prepareStatement(getDeleteOneSql());
+      try {
+        int cnt = 0;
+        for (final T o : instances) {
+          bindOneDelete(ps, o);
+          ps.addBatch();
+          cnt++;
+        }
+        execute(ps, cnt);
+      } finally {
+        ps.close();
+      }
+    } catch (SQLException e) {
+      throw new OrmException("Delete failure: " + getRelationName(), e);
+    }
+  }
+
+  private static void execute(final PreparedStatement ps, final int cnt)
+      throws SQLException {
+    if (cnt == 0) {
+      return;
+    }
+
+    final int[] states = ps.executeBatch();
+    if (states == null) {
+      throw new SQLException("No rows affected; expected " + cnt + " rows");
+    }
+    if (states.length != cnt) {
+      throw new SQLException("Expected " + cnt + " rows affected, received "
+          + states.length + " instead");
+    }
+    for (int i = 0; i < cnt; i++) {
+      if (states[i] != 1) {
+        throw new SQLException("Entity " + (i + 1) + " not affected by update");
+      }
+    }
+  }
+
+  protected abstract T newEntityInstance();
+
+  protected abstract String getRelationName();
+
+  protected abstract String getInsertOneSql();
+
+  protected abstract String getUpdateOneSql();
+
+  protected abstract String getDeleteOneSql();
+
+  protected abstract void bindOneInsert(PreparedStatement ps, T entity)
+      throws SQLException;
+
+  protected abstract void bindOneUpdate(PreparedStatement ps, T entity)
+      throws SQLException;
+
+  protected abstract void bindOneDelete(PreparedStatement ps, T entity)
+      throws SQLException;
+
+  protected abstract void bindOneFetch(ResultSet rs, T entity)
+      throws SQLException;
+}
diff --git a/src/com/google/gwtorm/jdbc/JdbcSchema.java b/src/com/google/gwtorm/jdbc/JdbcSchema.java
new file mode 100644
index 0000000..4327560
--- /dev/null
+++ b/src/com/google/gwtorm/jdbc/JdbcSchema.java
@@ -0,0 +1,105 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.jdbc;
+
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.Schema;
+import com.google.gwtorm.client.Transaction;
+import com.google.gwtorm.schema.RelationModel;
+import com.google.gwtorm.schema.SchemaModel;
+import com.google.gwtorm.schema.SequenceModel;
+import com.google.gwtorm.schema.sql.SqlDialect;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+/** Internal base class for implementations of {@link Schema}. */
+public abstract class JdbcSchema implements Schema {
+  private final Database<?> dbDef;
+  private Connection conn;
+
+  protected JdbcSchema(final Database<?> d, final Connection c) {
+    dbDef = d;
+    conn = c;
+  }
+
+  public final Connection getConnection() {
+    return conn;
+  }
+
+  public void createSchema() throws OrmException {
+    final SqlDialect dialect = dbDef.getDialect();
+    final SchemaModel model = dbDef.getSchemaModel();
+    try {
+      final Statement stmt;
+
+      stmt = getConnection().createStatement();
+      try {
+        for (final SequenceModel s : model.getSequences()) {
+          stmt.execute(s.getCreateSequenceSql(dialect));
+        }
+        for (final RelationModel r : model.getRelations()) {
+          stmt.execute(r.getCreateTableSql(dialect));
+        }
+      } finally {
+        stmt.close();
+      }
+    } catch (SQLException e) {
+      throw new OrmException("Schema creation failure", e);
+    }
+  }
+
+  protected long nextLong(final String query) throws OrmException {
+    try {
+      final Statement st = getConnection().createStatement();
+      try {
+        final ResultSet rs = st.executeQuery(query);
+        try {
+          if (!rs.next()) {
+            throw new SQLException("No result row for sequence query");
+          }
+          final long r = rs.getLong(1);
+          if (rs.next()) {
+            throw new SQLException("Too many results from sequence query");
+          }
+          return r;
+        } finally {
+          rs.close();
+        }
+      } finally {
+        st.close();
+      }
+    } catch (SQLException e) {
+      throw new OrmException("Sequence query failed", e);
+    }
+  }
+
+  public Transaction beginTransaction() {
+    return new JdbcTransaction(this);
+  }
+
+  public void close() {
+    if (conn != null) {
+      try {
+        conn.close();
+      } catch (SQLException err) {
+        // TODO Handle an exception while closing a connection
+      }
+      conn = null;
+    }
+  }
+}
diff --git a/src/com/google/gwtorm/jdbc/JdbcTransaction.java b/src/com/google/gwtorm/jdbc/JdbcTransaction.java
new file mode 100644
index 0000000..80cf804
--- /dev/null
+++ b/src/com/google/gwtorm/jdbc/JdbcTransaction.java
@@ -0,0 +1,102 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.jdbc;
+
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.Transaction;
+import com.google.gwtorm.client.impl.AbstractTransaction;
+
+import java.sql.SQLException;
+
+/** Implementation of the {@link Transaction} interface, on JDBC. */
+class JdbcTransaction extends AbstractTransaction {
+  private final JdbcSchema schema;
+  private boolean inProgress;
+  private boolean committed;
+
+  JdbcTransaction(final JdbcSchema s) {
+    schema = s;
+  }
+
+  @Override
+  public void commit() throws OrmException {
+    notCommitted();
+
+    if (!inProgress) {
+      try {
+        schema.getConnection().setAutoCommit(false);
+      } catch (SQLException e) {
+        throw new OrmException("Cannot start transaction", e);
+      }
+      inProgress = true;
+    }
+
+    try {
+      super.commit();
+    } catch (OrmException e) {
+      try {
+        rollback();
+      } catch (OrmException e2) {
+        // Ignore the cascaded rollback error.
+      }
+      throw e;
+    } catch (RuntimeException e) {
+      try {
+        rollback();
+      } catch (OrmException e2) {
+        // Ignore the cascaded rollback error.
+      }
+      throw e;
+    }
+
+    try {
+      schema.getConnection().commit();
+      committed = true;
+    } catch (SQLException e) {
+      throw new OrmException("Transaction failed", e);
+    } finally {
+      exitTransaction();
+    }
+  }
+
+  public void rollback() throws OrmException {
+    notCommitted();
+
+    if (inProgress) {
+      try {
+        schema.getConnection().rollback();
+      } catch (SQLException e) {
+        throw new OrmException("Rollback failed", e);
+      } finally {
+        exitTransaction();
+      }
+    }
+  }
+
+  private void notCommitted() throws OrmException {
+    if (committed) {
+      throw new OrmException("Transaction already committed");
+    }
+  }
+
+  private void exitTransaction() {
+    try {
+      schema.getConnection().setAutoCommit(true);
+    } catch (SQLException e) {
+    } finally {
+      inProgress = false;
+    }
+  }
+}
diff --git a/src/com/google/gwtorm/jdbc/gen/AccessGen.java b/src/com/google/gwtorm/jdbc/gen/AccessGen.java
new file mode 100644
index 0000000..4eba97d
--- /dev/null
+++ b/src/com/google/gwtorm/jdbc/gen/AccessGen.java
@@ -0,0 +1,541 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.jdbc.gen;
+
+import com.google.gwtorm.client.Access;
+import com.google.gwtorm.client.Key;
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.impl.ListResultSet;
+import com.google.gwtorm.jdbc.JdbcAccess;
+import com.google.gwtorm.jdbc.JdbcSchema;
+import com.google.gwtorm.schema.ColumnModel;
+import com.google.gwtorm.schema.KeyModel;
+import com.google.gwtorm.schema.QueryModel;
+import com.google.gwtorm.schema.RelationModel;
+import com.google.gwtorm.schema.Util;
+import com.google.gwtorm.schema.sql.SqlDialect;
+
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Iterator;
+import java.util.List;
+
+/** Generates a concrete implementation of an {@link Access} extension. */
+public class AccessGen implements Opcodes {
+  private static final String REL_ALIAS = "T";
+
+  private static enum DmlType {
+    INSERT("bindOneInsert"),
+
+    UPDATE("bindOneUpdate"),
+
+    DELETE("bindOneDelete");
+
+    final String methodName;
+
+    DmlType(final String m) {
+      methodName = m;
+    }
+  }
+
+  private final GeneratedClassLoader classLoader;
+  private final SchemaGen.RelationGen info;
+  private final RelationModel model;
+  private final SqlDialect dialect;
+
+  private ClassWriter cw;
+  private String superTypeName;
+  private String implClassName;
+  private String implTypeName;
+  private Type entityType;
+
+
+  public AccessGen(final GeneratedClassLoader loader,
+      final SchemaGen.RelationGen ri) {
+    classLoader = loader;
+    info = ri;
+    model = info.model;
+    dialect = ri.getDialect();
+    entityType =
+        Type.getObjectType(model.getEntityTypeClassName().replace('.', '/'));
+  }
+
+  public void defineClass() throws OrmException {
+    init();
+    implementConstructor();
+    implementGetString("getRelationName", model.getRelationName());
+    implementGetString("getInsertOneSql", model.getInsertOneSql(dialect));
+
+    if (model.getPrimaryKey() != null) {
+      if (model.getDependentColumns().isEmpty()) {
+        implementMissingGetString("getUpdateOneSql", "update");
+      } else {
+        implementGetString("getUpdateOneSql", model.getUpdateOneSql(dialect));
+      }
+      implementGetString("getDeleteOneSql", model.getDeleteOneSql(dialect));
+    } else {
+      implementMissingGetString("getUpdateOneSql", "update");
+      implementMissingGetString("getDeleteOneSql", "delete");
+    }
+
+    implementPrimaryKey();
+    implementGetOne();
+    implementNewEntityInstance();
+    implementBindOne(DmlType.INSERT);
+    implementBindOne(DmlType.UPDATE);
+    implementBindOne(DmlType.DELETE);
+    implementBindOneFetch();
+
+    if (model.getPrimaryKey() != null) {
+      implementKeyQuery(model.getPrimaryKey());
+    }
+
+    for (final QueryModel q : model.getQueries()) {
+      implementQuery(q);
+    }
+
+    cw.visitEnd();
+    classLoader.defineClass(implClassName, cw.toByteArray());
+    info.accessClassName = implClassName;
+  }
+
+
+  private void init() {
+    superTypeName = Type.getInternalName(JdbcAccess.class);
+    implClassName =
+        model.getEntityTypeClassName() + "_Access_" + model.getMethodName()
+            + "_" + Util.createRandomName();
+    implTypeName = implClassName.replace('.', '/');
+
+    cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
+    cw.visit(V1_3, ACC_PUBLIC | ACC_FINAL | ACC_SUPER, implTypeName, null,
+        superTypeName, new String[] {model.getAccessInterfaceName().replace(
+            '.', '/')});
+  }
+
+  private void implementConstructor() {
+    final String consName = "<init>";
+    final String consDesc =
+        Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {Type
+            .getType(JdbcSchema.class)});
+    final MethodVisitor mv;
+    mv = cw.visitMethod(ACC_PUBLIC, consName, consDesc, null, null);
+    mv.visitCode();
+    mv.visitVarInsn(ALOAD, 0);
+    mv.visitVarInsn(ALOAD, 1);
+    mv.visitMethodInsn(INVOKESPECIAL, superTypeName, consName, consDesc);
+    mv.visitInsn(RETURN);
+    mv.visitMaxs(-1, -1);
+    mv.visitEnd();
+  }
+
+  private void implementGetString(final String methodName,
+      final String returnValue) {
+    final MethodVisitor mv =
+        cw.visitMethod(ACC_PUBLIC | ACC_FINAL, methodName, Type
+            .getMethodDescriptor(Type.getType(String.class), new Type[] {}),
+            null, null);
+    mv.visitCode();
+    mv.visitLdcInsn(returnValue);
+    mv.visitInsn(ARETURN);
+    mv.visitMaxs(-1, -1);
+    mv.visitEnd();
+  }
+
+  private void implementMissingGetString(final String methodName,
+      final String why) {
+    final MethodVisitor mv =
+        cw.visitMethod(ACC_PUBLIC | ACC_FINAL, methodName, Type
+            .getMethodDescriptor(Type.getType(String.class), new Type[] {}),
+            null, null);
+    mv.visitCode();
+    throwUnsupported(mv, model.getMethodName() + " does not support " + why);
+    mv.visitInsn(RETURN);
+    mv.visitMaxs(-1, -1);
+    mv.visitEnd();
+  }
+
+  private void throwUnsupported(final MethodVisitor mv, final String message) {
+    final Type eType = Type.getType(UnsupportedOperationException.class);
+    mv.visitTypeInsn(NEW, eType.getInternalName());
+    mv.visitInsn(DUP);
+    mv.visitLdcInsn(message);
+    mv.visitMethodInsn(INVOKESPECIAL, eType.getInternalName(), "<init>", Type
+        .getMethodDescriptor(Type.VOID_TYPE, new Type[] {Type
+            .getType(String.class)}));
+    mv.visitInsn(ATHROW);
+  }
+
+  private void implementPrimaryKey() {
+    final KeyModel pk = model.getPrimaryKey();
+    final MethodVisitor mv =
+        cw.visitMethod(ACC_PUBLIC | ACC_FINAL, "primaryKey", Type
+            .getMethodDescriptor(Type.getType(Key.class), new Type[] {Type
+                .getType(Object.class)}), null, null);
+    mv.visitCode();
+    if (pk != null && pk.getField().isNested()) {
+      final ColumnModel pkf = pk.getField();
+      mv.visitVarInsn(ALOAD, 1);
+      mv.visitTypeInsn(CHECKCAST, entityType.getInternalName());
+      mv.visitFieldInsn(GETFIELD, entityType.getInternalName(), pkf
+          .getFieldName(), CodeGenSupport.toType(pkf).getDescriptor());
+    } else {
+      mv.visitInsn(ACONST_NULL);
+    }
+    mv.visitInsn(ARETURN);
+    mv.visitMaxs(-1, -1);
+    mv.visitEnd();
+  }
+
+  private void implementGetOne() {
+    final KeyModel pk = model.getPrimaryKey();
+
+    final MethodVisitor mv =
+        cw.visitMethod(ACC_PUBLIC | ACC_FINAL, "get", Type.getMethodDescriptor(
+            Type.getType(Object.class), new Type[] {Type.getType(Key.class)}),
+            null, new String[] {Type.getType(OrmException.class)
+                .getInternalName()});
+    mv.visitCode();
+    if (pk != null && pk.getField().isNested()) {
+      final Type keyType = CodeGenSupport.toType(pk.getField());
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitVarInsn(ALOAD, 1);
+      mv.visitTypeInsn(CHECKCAST, keyType.getInternalName());
+      mv.visitMethodInsn(INVOKEVIRTUAL, implTypeName, pk.getName(), Type
+          .getMethodDescriptor(entityType, new Type[] {keyType}));
+      mv.visitInsn(ARETURN);
+    } else {
+      throwUnsupported(mv, model.getMethodName() + " does not support get(Key)");
+    }
+    mv.visitMaxs(-1, -1);
+    mv.visitEnd();
+  }
+
+  private void implementNewEntityInstance() {
+    final MethodVisitor mv =
+        cw.visitMethod(ACC_PUBLIC | ACC_FINAL, "newEntityInstance", Type
+            .getMethodDescriptor(Type.getType(Object.class), new Type[] {}),
+            null, null);
+    mv.visitCode();
+    mv.visitTypeInsn(NEW, entityType.getInternalName());
+    mv.visitInsn(DUP);
+    mv.visitMethodInsn(INVOKESPECIAL, entityType.getInternalName(), "<init>",
+        Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {}));
+    mv.visitInsn(ARETURN);
+    mv.visitMaxs(-1, -1);
+    mv.visitEnd();
+  }
+
+  private void implementBindOne(final DmlType type) {
+    final MethodVisitor mv =
+        cw.visitMethod(ACC_PUBLIC | ACC_FINAL, type.methodName, Type
+            .getMethodDescriptor(Type.VOID_TYPE, new Type[] {
+                Type.getType(PreparedStatement.class),
+                Type.getType(Object.class)}), null, new String[] {Type.getType(
+            SQLException.class).getInternalName()});
+    mv.visitCode();
+
+    if (type != DmlType.INSERT && model.getPrimaryKey() == null) {
+      throwUnsupported(mv, model.getMethodName() + " has no primary key");
+      mv.visitInsn(RETURN);
+      mv.visitMaxs(-1, -1);
+      mv.visitEnd();
+    }
+
+    mv.visitVarInsn(ALOAD, 2);
+    mv.visitTypeInsn(CHECKCAST, entityType.getInternalName());
+    mv.visitVarInsn(ASTORE, 2);
+
+    final CodeGenSupport cgs = new CodeGenSupport(mv);
+    cgs.setEntityType(entityType);
+    if (type != DmlType.DELETE) {
+      for (final ColumnModel field : model.getDependentFields()) {
+        if (field.isNested()) {
+          final Label isnull = new Label();
+          final Label end = new Label();
+
+          cgs.setFieldReference(field, false);
+          cgs.pushFieldValue();
+          mv.visitJumpInsn(IFNULL, isnull);
+          for (final ColumnModel c : field.getAllLeafColumns()) {
+            cgs.setFieldReference(c);
+            dialect.getSqlTypeInfo(c).generatePreparedStatementSet(cgs);
+          }
+          mv.visitJumpInsn(GOTO, end);
+
+          mv.visitLabel(isnull);
+          for (final ColumnModel c : field.getAllLeafColumns()) {
+            cgs.setFieldReference(c);
+            dialect.getSqlTypeInfo(c).generatePreparedStatementNull(cgs);
+          }
+
+          mv.visitLabel(end);
+        } else {
+          cgs.setFieldReference(field);
+          dialect.getSqlTypeInfo(field).generatePreparedStatementSet(cgs);
+        }
+      }
+    }
+
+    for (final ColumnModel col : model.getPrimaryKeyColumns()) {
+      cgs.setFieldReference(col);
+      dialect.getSqlTypeInfo(col).generatePreparedStatementSet(cgs);
+    }
+
+    mv.visitInsn(RETURN);
+    mv.visitMaxs(-1, -1);
+    mv.visitEnd();
+  }
+
+  private void implementBindOneFetch() {
+    final MethodVisitor mv =
+        cw.visitMethod(ACC_PUBLIC | ACC_FINAL, "bindOneFetch", Type
+            .getMethodDescriptor(Type.VOID_TYPE, new Type[] {
+                Type.getType(ResultSet.class), Type.getType(Object.class)}),
+            null, new String[] {Type.getType(SQLException.class)
+                .getInternalName()});
+    mv.visitCode();
+
+    mv.visitVarInsn(ALOAD, 2);
+    mv.visitTypeInsn(CHECKCAST, entityType.getInternalName());
+    mv.visitVarInsn(ASTORE, 2);
+
+    final CodeGenSupport cgs = new CodeGenSupport(mv);
+    cgs.setEntityType(entityType);
+
+    if (model.getPrimaryKey() != null
+        && model.getPrimaryKey().getField().isNested()) {
+      final ColumnModel pkf = model.getPrimaryKey().getField();
+      final Type vType = CodeGenSupport.toType(pkf);
+      cgs.setFieldReference(pkf, false);
+      cgs.fieldSetBegin();
+      mv.visitTypeInsn(NEW, vType.getInternalName());
+      mv.visitInsn(DUP);
+      mv.visitMethodInsn(INVOKESPECIAL, vType.getInternalName(), "<init>", Type
+          .getMethodDescriptor(Type.VOID_TYPE, new Type[] {}));
+      cgs.fieldSetEnd();
+    }
+
+    for (final ColumnModel field : model.getDependentFields()) {
+      if (field.isNested()) {
+        final Type vType = CodeGenSupport.toType(field);
+        final Label islive = new Label();
+
+        cgs.setFieldReference(field, false);
+        cgs.fieldSetBegin();
+        mv.visitTypeInsn(NEW, vType.getInternalName());
+        mv.visitInsn(DUP);
+        mv.visitMethodInsn(INVOKESPECIAL, vType.getInternalName(), "<init>",
+            Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {}));
+        cgs.fieldSetEnd();
+
+        for (final ColumnModel c : field.getAllLeafColumns()) {
+          cgs.setFieldReference(c);
+          dialect.getSqlTypeInfo(c).generateResultSetGet(cgs);
+        }
+
+        cgs.pushSqlHandle();
+        mv.visitMethodInsn(INVOKEINTERFACE, Type.getType(ResultSet.class)
+            .getInternalName(), "wasNull", Type.getMethodDescriptor(
+            Type.BOOLEAN_TYPE, new Type[] {}));
+        mv.visitJumpInsn(IFEQ, islive);
+        cgs.setFieldReference(field, false);
+        cgs.fieldSetBegin();
+        mv.visitInsn(ACONST_NULL);
+        cgs.fieldSetEnd();
+        mv.visitLabel(islive);
+      } else {
+        cgs.setFieldReference(field);
+        dialect.getSqlTypeInfo(field).generateResultSetGet(cgs);
+      }
+    }
+    for (final ColumnModel col : model.getPrimaryKeyColumns()) {
+      cgs.setFieldReference(col);
+      dialect.getSqlTypeInfo(col).generateResultSetGet(cgs);
+    }
+
+    mv.visitInsn(RETURN);
+    mv.visitMaxs(-1, -1);
+    mv.visitEnd();
+  }
+
+  private void implementKeyQuery(final KeyModel info) {
+    final Type keyType = CodeGenSupport.toType(info.getField());
+    final StringBuilder query = new StringBuilder();
+    query.append(model.getSelectSql(dialect, REL_ALIAS));
+    query.append(" WHERE ");
+    int nth = 1;
+    for (final Iterator<ColumnModel> i = info.getAllLeafColumns().iterator(); i
+        .hasNext();) {
+      final ColumnModel c = i.next();
+      query.append(REL_ALIAS);
+      query.append('.');
+      query.append(c.getColumnName());
+      query.append('=');
+      query.append(dialect.getParameterPlaceHolder(nth++));
+      if (i.hasNext()) {
+        query.append(" AND ");
+      }
+    }
+
+    final MethodVisitor mv =
+        cw.visitMethod(ACC_PUBLIC | ACC_FINAL, info.getName(), Type
+            .getMethodDescriptor(entityType, new Type[] {keyType}), null,
+            new String[] {Type.getType(OrmException.class).getInternalName()});
+    mv.visitCode();
+
+    final int keyvar = 1, psvar = keyvar + keyType.getSize();
+    mv.visitVarInsn(ALOAD, 0);
+    mv.visitLdcInsn(query.toString());
+    mv.visitMethodInsn(INVOKEVIRTUAL, superTypeName, "prepareStatement", Type
+        .getMethodDescriptor(Type.getType(PreparedStatement.class),
+            new Type[] {Type.getType(String.class)}));
+    mv.visitVarInsn(ASTORE, psvar);
+
+    final CodeGenSupport cgs = new CodeGenSupport(mv) {
+      @Override
+      public void pushSqlHandle() {
+        mv.visitVarInsn(ALOAD, psvar);
+      }
+
+      @Override
+      public void pushFieldValue() {
+        appendGetField(getFieldReference());
+      }
+
+      @Override
+      protected void appendGetField(final ColumnModel c) {
+        if (c.getParent() == null) {
+          loadVar(keyType, keyvar);
+        } else {
+          super.appendGetField(c);
+        }
+      }
+    };
+    for (final ColumnModel c : info.getAllLeafColumns()) {
+      cgs.setFieldReference(c);
+      dialect.getSqlTypeInfo(c).generatePreparedStatementSet(cgs);
+    }
+
+    mv.visitVarInsn(ALOAD, 0);
+    mv.visitVarInsn(ALOAD, psvar);
+    mv.visitMethodInsn(INVOKEVIRTUAL, superTypeName, "queryOne", Type
+        .getMethodDescriptor(Type.getType(Object.class), new Type[] {Type
+            .getType(PreparedStatement.class)}));
+    mv.visitTypeInsn(CHECKCAST, entityType.getInternalName());
+    mv.visitInsn(ARETURN);
+    mv.visitMaxs(-1, -1);
+    mv.visitEnd();
+  }
+
+  private void implementQuery(final QueryModel info) {
+    final List<ColumnModel> pCols = info.getParameters();
+    final boolean hasLimitParam = info.hasLimitParameter();
+    final Type[] pTypes = new Type[pCols.size() + (hasLimitParam ? 1 : 0)];
+    final int[] pVars = new int[pTypes.length];
+    int nextVar = 1;
+    for (int i = 0; i < pCols.size(); i++) {
+      pTypes[i] = CodeGenSupport.toType(pCols.get(i));
+      pVars[i] = nextVar;
+      nextVar += pTypes[i].getSize();
+    }
+    if (hasLimitParam) {
+      pTypes[pTypes.length - 1] = Type.INT_TYPE;
+      pVars[pTypes.length - 1] = nextVar;
+      nextVar += Type.INT_TYPE.getSize();
+    }
+
+    final int psvar = nextVar++;
+    final MethodVisitor mv =
+        cw.visitMethod(ACC_PUBLIC | ACC_FINAL, info.getName(), Type
+            .getMethodDescriptor(Type
+                .getType(com.google.gwtorm.client.ResultSet.class), pTypes),
+            null, new String[] {Type.getType(OrmException.class)
+                .getInternalName()});
+    mv.visitCode();
+
+    mv.visitVarInsn(ALOAD, 0);
+    mv.visitLdcInsn(info.getSelectSql(dialect, REL_ALIAS));
+    mv.visitMethodInsn(INVOKEVIRTUAL, superTypeName, "prepareStatement", Type
+        .getMethodDescriptor(Type.getType(PreparedStatement.class),
+            new Type[] {Type.getType(String.class)}));
+    mv.visitVarInsn(ASTORE, psvar);
+
+    final int argIdx[] = new int[] {0};
+    final CodeGenSupport cgs = new CodeGenSupport(mv) {
+      @Override
+      public void pushSqlHandle() {
+        mv.visitVarInsn(ALOAD, psvar);
+      }
+
+      @Override
+      public void pushFieldValue() {
+        appendGetField(getFieldReference());
+      }
+
+      @Override
+      protected void appendGetField(final ColumnModel c) {
+        final int n = argIdx[0];
+        if (c == pCols.get(n)) {
+          loadVar(pTypes[n], pVars[n]);
+        } else {
+          super.appendGetField(c);
+        }
+      }
+    };
+    for (final ColumnModel c : pCols) {
+      if (c.isNested()) {
+        for (final ColumnModel n : c.getAllLeafColumns()) {
+          cgs.setFieldReference(n);
+          dialect.getSqlTypeInfo(n).generatePreparedStatementSet(cgs);
+        }
+      } else {
+        cgs.setFieldReference(c);
+        dialect.getSqlTypeInfo(c).generatePreparedStatementSet(cgs);
+      }
+      argIdx[0]++;
+    }
+
+    if (info.hasLimit()) {
+      if (hasLimitParam || !dialect.selectHasLimit()) {
+        mv.visitVarInsn(ALOAD, psvar);
+        if (hasLimitParam) {
+          mv.visitVarInsn(ILOAD, pVars[pTypes.length - 1]);
+        } else {
+          cgs.push(info.getStaticLimit());
+        }
+        mv.visitMethodInsn(INVOKEINTERFACE, Type.getType(
+            PreparedStatement.class).getInternalName(), "setMaxRows", Type
+            .getMethodDescriptor(Type.VOID_TYPE, new Type[] {Type.INT_TYPE}));
+      }
+    }
+
+    mv.visitVarInsn(ALOAD, 0);
+    mv.visitVarInsn(ALOAD, psvar);
+    mv.visitMethodInsn(INVOKEVIRTUAL, superTypeName, "queryList", Type
+        .getMethodDescriptor(Type.getType(ListResultSet.class),
+            new Type[] {Type.getType(PreparedStatement.class)}));
+    mv.visitInsn(ARETURN);
+    mv.visitMaxs(-1, -1);
+    mv.visitEnd();
+  }
+}
diff --git a/src/com/google/gwtorm/jdbc/gen/CodeGenSupport.java b/src/com/google/gwtorm/jdbc/gen/CodeGenSupport.java
new file mode 100644
index 0000000..97bf2f1
--- /dev/null
+++ b/src/com/google/gwtorm/jdbc/gen/CodeGenSupport.java
@@ -0,0 +1,178 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.jdbc.gen;
+
+import com.google.gwtorm.schema.ColumnModel;
+
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.lang.reflect.Method;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+
+public class CodeGenSupport implements Opcodes {
+  public final MethodVisitor mv;
+  private ColumnModel col;
+  private int columnIdx;
+  private Type entityType;
+
+  public CodeGenSupport(final MethodVisitor method) {
+    mv = method;
+  }
+
+  public void push(final int val) {
+    switch (val) {
+      case -1:
+        mv.visitInsn(ICONST_M1);
+        break;
+      case 0:
+        mv.visitInsn(ICONST_0);
+        break;
+      case 1:
+        mv.visitInsn(ICONST_1);
+        break;
+      case 2:
+        mv.visitInsn(ICONST_2);
+        break;
+      case 3:
+        mv.visitInsn(ICONST_3);
+        break;
+      case 4:
+        mv.visitInsn(ICONST_4);
+        break;
+      case 5:
+        mv.visitInsn(ICONST_5);
+        break;
+      default:
+        if (Byte.MIN_VALUE >= val && val < Byte.MAX_VALUE) {
+          mv.visitIntInsn(BIPUSH, val);
+        } else if (Short.MIN_VALUE >= val && val < Short.MAX_VALUE) {
+          mv.visitIntInsn(SIPUSH, val);
+        } else {
+          mv.visitLdcInsn(Integer.valueOf(val));
+        }
+        break;
+    }
+  }
+
+  public void loadVar(final Type type, final int index) {
+    mv.visitVarInsn(type.getOpcode(ILOAD), index);
+  }
+
+  public void setEntityType(final Type et) {
+    entityType = et;
+  }
+
+  public void setFieldReference(final ColumnModel cm) {
+    setFieldReference(cm, true);
+  }
+
+  public void setFieldReference(final ColumnModel cm, boolean incr) {
+    col = cm;
+    if (incr) {
+      columnIdx++;
+    }
+  }
+
+  public ColumnModel getFieldReference() {
+    return col;
+  }
+
+  public void pushSqlHandle() {
+    mv.visitVarInsn(ALOAD, 1);
+  }
+
+  public void pushEntity() {
+    mv.visitVarInsn(ALOAD, 2);
+  }
+
+  public void pushColumnIndex() {
+    push(columnIdx);
+  }
+
+  public void invokePreparedStatementSet(final String sqlTypeName) {
+    final Method m;
+    try {
+      m =
+          PreparedStatement.class.getMethod("set" + sqlTypeName, Integer.TYPE,
+              ResultSet.class.getMethod("get" + sqlTypeName, Integer.TYPE)
+                  .getReturnType());
+    } catch (SecurityException e) {
+      throw new RuntimeException("java.sql has no " + sqlTypeName);
+    } catch (NoSuchMethodException e) {
+      throw new RuntimeException("java.sql has no " + sqlTypeName, e);
+    }
+    mv.visitMethodInsn(INVOKEINTERFACE, Type
+        .getInternalName(PreparedStatement.class), m.getName(), Type
+        .getMethodDescriptor(m));
+  }
+
+  public void invokeResultSetGet(final String sqlTypeName) {
+    final Method m;
+    try {
+      m = ResultSet.class.getMethod("get" + sqlTypeName, Integer.TYPE);
+    } catch (SecurityException e) {
+      throw new RuntimeException("java.sql has no " + sqlTypeName);
+    } catch (NoSuchMethodException e) {
+      throw new RuntimeException("java.sql has no " + sqlTypeName, e);
+    }
+    mv.visitMethodInsn(INVOKEINTERFACE, Type.getInternalName(ResultSet.class),
+        m.getName(), Type.getMethodDescriptor(m));
+  }
+
+  public void fieldSetBegin() {
+    pushEntity();
+    if (col.getParent() != null) {
+      appendGetField(col.getParent());
+    }
+  }
+
+  public void fieldSetEnd() {
+    final Type c = containerClass(col);
+    mv.visitFieldInsn(PUTFIELD, c.getInternalName(), col.getFieldName(),
+        toType(col).getDescriptor());
+  }
+
+  public void pushFieldValue() {
+    pushEntity();
+    appendGetField(col);
+  }
+
+  protected void appendGetField(final ColumnModel c) {
+    if (c.getParent() != null) {
+      appendGetField(c.getParent());
+    }
+    final Type t = containerClass(c);
+    mv.visitFieldInsn(GETFIELD, t.getInternalName(), c.getFieldName(),
+        toType(c).getDescriptor());
+  }
+
+  private Type containerClass(final ColumnModel c) {
+    if (c.getParent() == null) {
+      return entityType;
+    }
+    final String n = c.getParent().getNestedClassName();
+    return Type.getObjectType(n.replace('.', '/'));
+  }
+
+  static Type toType(final ColumnModel c) {
+    if (c.isSqlPrimitive()) {
+      return Type.getType(c.getPrimitiveType());
+    }
+    return Type.getObjectType(c.getNestedClassName().replace('.', '/'));
+  }
+}
diff --git a/src/com/google/gwtorm/jdbc/gen/GeneratedClassLoader.java b/src/com/google/gwtorm/jdbc/gen/GeneratedClassLoader.java
new file mode 100644
index 0000000..53643d3
--- /dev/null
+++ b/src/com/google/gwtorm/jdbc/gen/GeneratedClassLoader.java
@@ -0,0 +1,90 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.jdbc.gen;
+
+import com.google.gwtorm.client.OrmException;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Hacked ClassLoader to inject generated code into the parent.
+ * <p>
+ * This ClassLoader allows our code generators to inject their generated classes
+ * into the parent ClassLoader, which should be the same ClassLoader that
+ * defined the application's Schema interface extension. This is necessary to
+ * ensure the generated classes can access protected and default-access fields
+ * within the entities.
+ */
+public class GeneratedClassLoader extends ClassLoader {
+  private static final boolean debugCodeGen;
+
+  private static final Method defineClass;
+
+  static {
+    debugCodeGen = "true".equals(System.getProperty("gwtorm.debugCodeGen"));
+
+    Method m;
+    try {
+      m =
+          ClassLoader.class.getDeclaredMethod("defineClass", String.class,
+              byte[].class, Integer.TYPE, Integer.TYPE);
+      m.setAccessible(true);
+    } catch (SecurityException e) {
+      throw new LinkageError("No defineClass in ClassLoader");
+    } catch (NoSuchMethodException e) {
+      throw new LinkageError("No defineClass in ClassLoader");
+    }
+    defineClass = m;
+  }
+
+  public GeneratedClassLoader(final ClassLoader parent) {
+    super(parent);
+  }
+
+  void defineClass(final String name, final byte[] code) throws OrmException {
+    if (debugCodeGen) {
+      final File outClassFile =
+          new File("generated_classes/" + name.replace('.', '/') + ".class");
+      outClassFile.getParentFile().mkdirs();
+      try {
+        final FileOutputStream out = new FileOutputStream(outClassFile);
+        try {
+          out.write(code);
+        } finally {
+          out.close();
+        }
+      } catch (IOException e) {
+        throw new OrmException("Cannot save debug class " + outClassFile, e);
+      }
+    }
+
+    try {
+      defineClass.invoke(getParent(), name, code, Integer.valueOf(0), Integer
+          .valueOf(code.length));
+    } catch (IllegalArgumentException e) {
+      throw new OrmException("Unable to inject class " + name, e);
+    } catch (SecurityException e) {
+      throw new OrmException("Unable to inject class " + name, e);
+    } catch (IllegalAccessException e) {
+      throw new OrmException("Unable to inject class " + name, e);
+    } catch (InvocationTargetException e) {
+      throw new OrmException("Unable to inject class " + name, e);
+    }
+  }
+}
diff --git a/src/com/google/gwtorm/jdbc/gen/SchemaFactoryGen.java b/src/com/google/gwtorm/jdbc/gen/SchemaFactoryGen.java
new file mode 100644
index 0000000..094888c
--- /dev/null
+++ b/src/com/google/gwtorm/jdbc/gen/SchemaFactoryGen.java
@@ -0,0 +1,114 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.jdbc.gen;
+
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.Schema;
+import com.google.gwtorm.jdbc.AbstractSchemaFactory;
+import com.google.gwtorm.jdbc.Database;
+import com.google.gwtorm.schema.Util;
+
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.sql.Connection;
+
+/** Generates a factory to efficiently create new Schema instances. */
+public class SchemaFactoryGen<T extends Schema> implements Opcodes {
+  private final GeneratedClassLoader classLoader;
+  private final SchemaGen schemaGen;
+  private ClassWriter cw;
+  private String superTypeName;
+  private String implClassName;
+  private String implTypeName;
+
+  public SchemaFactoryGen(final GeneratedClassLoader loader, final SchemaGen gen) {
+    classLoader = loader;
+    schemaGen = gen;
+  }
+
+  public void defineClass() throws OrmException {
+    init();
+    implementEmptyConstructor();
+    implementCreate();
+    cw.visitEnd();
+    classLoader.defineClass(implClassName, cw.toByteArray());
+  }
+
+  public AbstractSchemaFactory<T> create() throws OrmException {
+    defineClass();
+    try {
+      return cast(Class.forName(implClassName, true, classLoader).newInstance());
+    } catch (InstantiationException e) {
+      throw new OrmException("Cannot create schema factory", e);
+    } catch (IllegalAccessException e) {
+      throw new OrmException("Cannot create schema factory", e);
+    } catch (ClassNotFoundException e) {
+      throw new OrmException("Cannot create schema factory", e);
+    }
+  }
+
+  @SuppressWarnings("unchecked")
+  private AbstractSchemaFactory<T> cast(final Object newInstance) {
+    return (AbstractSchemaFactory<T>) newInstance;
+  }
+
+  private void init() {
+    superTypeName = Type.getInternalName(AbstractSchemaFactory.class);
+    implClassName =
+        schemaGen.getSchemaClassName() + "_Factory_" + Util.createRandomName();
+    implTypeName = implClassName.replace('.', '/');
+
+    cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
+    cw.visit(V1_3, ACC_PUBLIC | ACC_FINAL | ACC_SUPER, implTypeName, null,
+        superTypeName, null);
+  }
+
+  private void implementEmptyConstructor() {
+    final String consName = "<init>";
+    final String consDesc =
+        Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {});
+    final MethodVisitor mv;
+    mv = cw.visitMethod(ACC_PUBLIC, consName, consDesc, null, null);
+    mv.visitCode();
+    mv.visitVarInsn(ALOAD, 0);
+    mv.visitMethodInsn(INVOKESPECIAL, superTypeName, consName, consDesc);
+    mv.visitInsn(RETURN);
+    mv.visitMaxs(-1, -1);
+    mv.visitEnd();
+  }
+
+  private void implementCreate() {
+    final MethodVisitor mv =
+        cw.visitMethod(ACC_PUBLIC | ACC_FINAL, "create", Type
+            .getMethodDescriptor(Type.getType(Schema.class), new Type[] {
+                Type.getType(Database.class), Type.getType(Connection.class)}),
+            null, null);
+    mv.visitCode();
+
+    mv.visitTypeInsn(NEW, schemaGen.getImplTypeName());
+    mv.visitInsn(DUP);
+    mv.visitVarInsn(ALOAD, 1);
+    mv.visitVarInsn(ALOAD, 2);
+    mv.visitMethodInsn(INVOKESPECIAL, schemaGen.getImplTypeName(), "<init>",
+        Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {
+            Type.getType(Database.class), Type.getType(Connection.class)}));
+    mv.visitInsn(ARETURN);
+    mv.visitMaxs(-1, -1);
+    mv.visitEnd();
+  }
+}
diff --git a/src/com/google/gwtorm/jdbc/gen/SchemaGen.java b/src/com/google/gwtorm/jdbc/gen/SchemaGen.java
new file mode 100644
index 0000000..ee1bb89
--- /dev/null
+++ b/src/com/google/gwtorm/jdbc/gen/SchemaGen.java
@@ -0,0 +1,205 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.jdbc.gen;
+
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.Schema;
+import com.google.gwtorm.jdbc.Database;
+import com.google.gwtorm.jdbc.JdbcSchema;
+import com.google.gwtorm.schema.RelationModel;
+import com.google.gwtorm.schema.SequenceModel;
+import com.google.gwtorm.schema.Util;
+import com.google.gwtorm.schema.java.JavaSchemaModel;
+import com.google.gwtorm.schema.sql.SqlDialect;
+
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.sql.Connection;
+import java.util.ArrayList;
+import java.util.List;
+
+/** Generates a concrete implementation of a {@link Schema} extension. */
+public class SchemaGen implements Opcodes {
+  private final GeneratedClassLoader classLoader;
+  private final JavaSchemaModel schema;
+  private final SqlDialect dialect;
+  private List<RelationGen> relations;
+  private ClassWriter cw;
+  private String superTypeName;
+  private String implClassName;
+  private String implTypeName;
+
+  public SchemaGen(final GeneratedClassLoader loader,
+      final JavaSchemaModel schemaModel, final SqlDialect sqlDialect) {
+    classLoader = loader;
+    schema = schemaModel;
+    dialect = sqlDialect;
+  }
+
+  public void defineClass() throws OrmException {
+    defineRelationClasses();
+
+    init();
+    implementRelationFields();
+    implementConstructor();
+    implementSequenceMethods();
+    implementRelationMethods();
+    cw.visitEnd();
+    classLoader.defineClass(getImplClassName(), cw.toByteArray());
+  }
+
+  String getSchemaClassName() {
+    return schema.getSchemaClassName();
+  }
+
+  String getImplClassName() {
+    return implClassName;
+  }
+
+  String getImplTypeName() {
+    return implTypeName;
+  }
+
+  private void defineRelationClasses() throws OrmException {
+    relations = new ArrayList<RelationGen>();
+    for (final RelationModel rel : schema.getRelations()) {
+      final RelationGen g = new RelationGen(rel);
+      relations.add(g);
+      new AccessGen(classLoader, g).defineClass();
+    }
+  }
+
+  private void init() {
+    superTypeName = Type.getInternalName(JdbcSchema.class);
+    implClassName = getSchemaClassName() + "_Schema_" + Util.createRandomName();
+    implTypeName = implClassName.replace('.', '/');
+
+    cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
+    cw.visit(V1_3, ACC_PUBLIC | ACC_FINAL | ACC_SUPER, implTypeName, null,
+        superTypeName, new String[] {getSchemaClassName().replace('.', '/')});
+  }
+
+  private void implementRelationFields() {
+    for (final RelationGen info : relations) {
+      info.implementField();
+    }
+  }
+
+  private void implementConstructor() {
+    final String consName = "<init>";
+    final String consDesc =
+        Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {
+            Type.getType(Database.class), Type.getType(Connection.class)});
+    final MethodVisitor mv =
+        cw.visitMethod(ACC_PUBLIC, consName, consDesc, null, null);
+    mv.visitCode();
+
+    mv.visitVarInsn(ALOAD, 0);
+    mv.visitVarInsn(ALOAD, 1);
+    mv.visitVarInsn(ALOAD, 2);
+    mv.visitMethodInsn(INVOKESPECIAL, superTypeName, consName, consDesc);
+
+    for (final RelationGen info : relations) {
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitTypeInsn(NEW, info.accessType.getInternalName());
+      mv.visitInsn(DUP);
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitMethodInsn(INVOKESPECIAL, info.accessType.getInternalName(),
+          consName, Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {Type
+              .getType(JdbcSchema.class)}));
+      mv.visitFieldInsn(PUTFIELD, implTypeName, info
+          .getAccessInstanceFieldName(), info.accessType.getDescriptor());
+    }
+
+    mv.visitInsn(RETURN);
+    mv.visitMaxs(-1, -1);
+    mv.visitEnd();
+  }
+
+  private void implementSequenceMethods() {
+    for (final SequenceModel seq : schema.getSequences()) {
+      final Type retType = Type.getType(seq.getResultType());
+      final MethodVisitor mv =
+          cw
+              .visitMethod(ACC_PUBLIC, seq.getMethodName(), Type
+                  .getMethodDescriptor(retType, new Type[] {}), null,
+                  new String[] {Type.getType(OrmException.class)
+                      .getInternalName()});
+      mv.visitCode();
+
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitLdcInsn(dialect.getNextSequenceValueSql(seq.getSequenceName()));
+      mv.visitMethodInsn(INVOKEVIRTUAL, superTypeName, "nextLong", Type
+          .getMethodDescriptor(Type.getType(Long.TYPE), new Type[] {Type
+              .getType(String.class)}));
+      if (retType.getSize() == 1) {
+        mv.visitInsn(L2I);
+        mv.visitInsn(IRETURN);
+      } else {
+        mv.visitInsn(LRETURN);
+      }
+      mv.visitMaxs(-1, -1);
+      mv.visitEnd();
+    }
+  }
+
+  private void implementRelationMethods() {
+    for (final RelationGen info : relations) {
+      info.implementMethod();
+    }
+  }
+
+  class RelationGen {
+    final RelationModel model;
+    String accessClassName;
+    Type accessType;
+
+    RelationGen(final RelationModel model) {
+      this.model = model;
+    }
+
+    SqlDialect getDialect() {
+      return SchemaGen.this.dialect;
+    }
+
+    void implementField() {
+      accessType = Type.getObjectType(accessClassName.replace('.', '/'));
+      cw.visitField(ACC_PRIVATE | ACC_FINAL, getAccessInstanceFieldName(),
+          accessType.getDescriptor(), null, null).visitEnd();
+    }
+
+    String getAccessInstanceFieldName() {
+      return "access_" + model.getMethodName();
+    }
+
+    void implementMethod() {
+      final MethodVisitor mv =
+          cw.visitMethod(ACC_PUBLIC | ACC_FINAL, model.getMethodName(), Type
+              .getMethodDescriptor(Type.getObjectType(model
+                  .getAccessInterfaceName().replace('.', '/')), new Type[] {}),
+              null, null);
+      mv.visitCode();
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitFieldInsn(GETFIELD, implTypeName, getAccessInstanceFieldName(),
+          accessType.getDescriptor());
+      mv.visitInsn(ARETURN);
+      mv.visitMaxs(-1, -1);
+      mv.visitEnd();
+    }
+  }
+}
diff --git a/src/com/google/gwtorm/rebind/AccessCreator.java b/src/com/google/gwtorm/rebind/AccessCreator.java
new file mode 100644
index 0000000..240d748
--- /dev/null
+++ b/src/com/google/gwtorm/rebind/AccessCreator.java
@@ -0,0 +1,111 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.rebind;
+
+import com.google.gwt.core.ext.GeneratorContext;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JArrayType;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JPackage;
+import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
+import com.google.gwt.user.rebind.SourceWriter;
+
+import java.io.PrintWriter;
+
+class AccessCreator {
+  private static final String ACCESS_SUFFIX = "_DataAccess";
+  private JClassType svcInf;
+
+  AccessCreator(final JClassType remoteService) {
+    svcInf = remoteService;
+  }
+
+  String create(final TreeLogger logger, final GeneratorContext context)
+      throws UnableToCompleteException {
+    final TypeOracle typeOracle = context.getTypeOracle();
+
+    final SourceWriter srcWriter = getSourceWriter(logger, context);
+    if (srcWriter == null) {
+      return getProxyQualifiedName();
+    }
+
+    srcWriter.commit(logger);
+
+    return getProxyQualifiedName();
+  }
+
+  private SourceWriter getSourceWriter(final TreeLogger logger,
+      final GeneratorContext ctx) {
+    final JPackage servicePkg = svcInf.getPackage();
+    final String pkgName = servicePkg == null ? "" : servicePkg.getName();
+    final PrintWriter pw;
+    final ClassSourceFileComposerFactory cf;
+
+    pw = ctx.tryCreate(logger, pkgName, getProxySimpleName());
+    if (pw == null) {
+      return null;
+    }
+
+    cf = new ClassSourceFileComposerFactory(pkgName, getProxySimpleName());
+    cf.addImplementedInterface(svcInf.getErasedType().getQualifiedSourceName());
+    return cf.createSourceWriter(ctx, pw);
+  }
+
+  private String getProxyQualifiedName() {
+    final String[] name = synthesizeTopLevelClassName(svcInf, ACCESS_SUFFIX);
+    return name[0].length() == 0 ? name[1] : name[0] + "." + name[1];
+  }
+
+  private String getProxySimpleName() {
+    return synthesizeTopLevelClassName(svcInf, ACCESS_SUFFIX)[1];
+  }
+
+  static String[] synthesizeTopLevelClassName(JClassType type, String suffix) {
+    // Gets the basic name of the type. If it's a nested type, the type name
+    // will contains dots.
+    //
+    String className;
+    String packageName;
+
+    JType leafType = type.getLeafType();
+    if (leafType.isPrimitive() != null) {
+      className = leafType.getSimpleSourceName();
+      packageName = "";
+    } else {
+      JClassType classOrInterface = leafType.isClassOrInterface();
+      assert (classOrInterface != null);
+      className = classOrInterface.getName();
+      packageName = classOrInterface.getPackage().getName();
+    }
+
+    JArrayType isArray = type.isArray();
+    if (isArray != null) {
+      className += "_Array_Rank_" + isArray.getRank();
+    }
+
+    // Add the meaningful suffix.
+    //
+    className += suffix;
+
+    // Make it a top-level name.
+    //
+    className = className.replace('.', '_');
+
+    return new String[] {packageName, className};
+  }
+}
diff --git a/src/com/google/gwtorm/rebind/DataAccessGenerator.java b/src/com/google/gwtorm/rebind/DataAccessGenerator.java
new file mode 100644
index 0000000..31a9547
--- /dev/null
+++ b/src/com/google/gwtorm/rebind/DataAccessGenerator.java
@@ -0,0 +1,53 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.rebind;
+
+import com.google.gwt.core.ext.Generator;
+import com.google.gwt.core.ext.GeneratorContext;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+
+public class DataAccessGenerator extends Generator {
+  @Override
+  public String generate(final TreeLogger logger, final GeneratorContext ctx,
+      final String requestedClass) throws UnableToCompleteException {
+
+    final TypeOracle typeOracle = ctx.getTypeOracle();
+    assert (typeOracle != null);
+
+    final JClassType remoteService = typeOracle.findType(requestedClass);
+    if (remoteService == null) {
+      logger.log(TreeLogger.ERROR, "Unable to find metadata for type '"
+          + requestedClass + "'", null);
+      throw new UnableToCompleteException();
+    }
+
+    if (remoteService.isInterface() == null) {
+      logger.log(TreeLogger.ERROR, remoteService.getQualifiedSourceName()
+          + " is not an interface", null);
+      throw new UnableToCompleteException();
+    }
+
+    AccessCreator proxyCreator = new AccessCreator(remoteService);
+
+    TreeLogger proxyLogger =
+        logger.branch(TreeLogger.DEBUG, "Generating data access for '"
+            + remoteService.getQualifiedSourceName() + "'", null);
+
+    return proxyCreator.create(proxyLogger, ctx);
+  }
+}
diff --git a/src/com/google/gwtorm/schema/ColumnModel.java b/src/com/google/gwtorm/schema/ColumnModel.java
new file mode 100644
index 0000000..8122d52
--- /dev/null
+++ b/src/com/google/gwtorm/schema/ColumnModel.java
@@ -0,0 +1,133 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.schema;
+
+import com.google.gwtorm.client.Column;
+import com.google.gwtorm.client.OrmException;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+
+public abstract class ColumnModel {
+  protected ColumnModel parent;
+  private String origName;
+  protected String columnName;
+  protected Column column;
+  protected Collection<ColumnModel> nestedColumns;
+  protected boolean inPrimaryKey;
+
+  protected ColumnModel() {
+    nestedColumns = Collections.<ColumnModel> emptyList();
+  }
+
+  protected void initName(final String fieldName, final Column col)
+      throws OrmException {
+    if (col == null) {
+      throw new OrmException("Field " + fieldName + " is missing "
+          + Column.class.getName() + " annotation");
+    }
+    column = col;
+    origName = Util.any(column.name(), fieldName);
+    columnName = origName;
+  }
+
+  protected void initNestedColumns(final Collection<? extends ColumnModel> col)
+      throws OrmException {
+    if (col == null || col.isEmpty()) {
+      throw new OrmException("Field " + getPathToFieldName()
+          + " has no nested members inside type " + getNestedClassName());
+    }
+
+    nestedColumns = new ArrayList<ColumnModel>(col);
+    recomputeColumnNames();
+  }
+
+  private void recomputeColumnNames() {
+    for (final ColumnModel c : nestedColumns) {
+      c.parent = this;
+      if (nestedColumns.size() == 1) {
+        c.columnName = columnName;
+      } else {
+        c.columnName = columnName + "_" + c.origName;
+      }
+      c.recomputeColumnNames();
+    }
+  }
+
+  public Collection<ColumnModel> getNestedColumns() {
+    return nestedColumns;
+  }
+
+  public Collection<ColumnModel> getAllLeafColumns() {
+    final ArrayList<ColumnModel> r = new ArrayList<ColumnModel>();
+    for (final ColumnModel c : nestedColumns) {
+      if (c.isNested()) {
+        r.addAll(c.getNestedColumns());
+      } else {
+        r.add(c);
+      }
+    }
+    return r;
+  }
+
+  public ColumnModel getParent() {
+    return parent;
+  }
+
+  public String getPathToFieldName() {
+    if (getParent() == null) {
+      return getFieldName();
+    }
+    return getParent().getPathToFieldName() + "." + getFieldName();
+  }
+
+  public String getColumnName() {
+    return columnName;
+  }
+
+  public boolean isSqlPrimitive() {
+    return getPrimitiveType() != null;
+  }
+
+  public boolean isNested() {
+    return getPrimitiveType() == null;
+  }
+
+  public Column getColumnAnnotation() {
+    return column;
+  }
+
+  public abstract String getFieldName();
+
+  public abstract Class<?> getPrimitiveType();
+
+  public abstract String getNestedClassName();
+
+  @Override
+  public String toString() {
+    final StringBuilder r = new StringBuilder();
+    r.append("Column[\n");
+    r.append("  field:    " + getPathToFieldName() + "\n");
+    r.append("  column:   " + getColumnName() + "\n");
+    if (isSqlPrimitive()) {
+      r.append("  type:     " + getPrimitiveType().getName() + "\n");
+    } else {
+      r.append("  contains: " + getNestedClassName() + "\n");
+    }
+    r.append("]");
+    return r.toString();
+  }
+}
diff --git a/src/com/google/gwtorm/schema/KeyModel.java b/src/com/google/gwtorm/schema/KeyModel.java
new file mode 100644
index 0000000..d298ffa
--- /dev/null
+++ b/src/com/google/gwtorm/schema/KeyModel.java
@@ -0,0 +1,58 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.schema;
+
+import java.util.Collection;
+import java.util.Collections;
+
+public class KeyModel {
+  protected final String accessName;
+  protected final ColumnModel key;
+
+  public KeyModel(final String name, final ColumnModel keyField) {
+    accessName = name;
+    key = keyField;
+  }
+
+  public String getName() {
+    return accessName;
+  }
+
+  public ColumnModel getField() {
+    return key;
+  }
+
+  public Collection<ColumnModel> getAllLeafColumns() {
+    if (key.isNested()) {
+      return key.getAllLeafColumns();
+    }
+    return Collections.singleton(key);
+  }
+
+  @Override
+  public String toString() {
+    final StringBuilder r = new StringBuilder();
+    r.append("Key[");
+    r.append(getName());
+    r.append(" / ");
+    r.append(key.getPathToFieldName());
+    r.append(":");
+    for (final ColumnModel c : getAllLeafColumns()) {
+      r.append(" " + c.getColumnName());
+    }
+    r.append("]");
+    return r.toString();
+  }
+}
diff --git a/src/com/google/gwtorm/schema/Query.g b/src/com/google/gwtorm/schema/Query.g
new file mode 100644
index 0000000..e097764
--- /dev/null
+++ b/src/com/google/gwtorm/schema/Query.g
@@ -0,0 +1,215 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+grammar Query;
+options {
+  language = Java;
+  output = AST;
+}
+
+tokens {
+  WHERE;
+  ORDER;
+  BY;
+  AND;
+  LT;
+  LE;
+  GT;
+  GE;
+  EQ;
+  NE;
+  ID;
+  PLACEHOLDER;
+  COMMA;
+  ASC;
+  DESC;
+  LIMIT;
+  CONSTANT_INTEGER;
+  CONSTANT_STRING;
+  TRUE;
+  FALSE;
+}
+
+@header {
+package com.google.gwtorm.schema;
+}
+@members {
+    public static Tree parse(final RelationModel m, final String str)
+      throws QueryParseException {
+      try {
+        final QueryParser p = new QueryParser(
+          new TokenRewriteStream(
+            new QueryLexer(
+              new ANTLRStringStream(str)
+            )
+          )
+        );
+        p.relationModel = m;
+        return (Tree)p.query().getTree();
+      } catch (QueryParseInternalException e) {
+        throw new QueryParseException(e.getMessage());
+      } catch (RecognitionException e) {
+        throw new QueryParseException(e.getMessage());
+      }
+    }
+
+    public static class Column extends CommonTree {
+      private final ColumnModel field;
+
+      public Column(int ttype, Token t, final RelationModel relationModel) {
+        token = t;
+        field = relationModel.getField(t.getText());
+        if (field == null) {
+          throw new QueryParseInternalException("No field " + t.getText());
+        }
+      }
+
+      public Column(final Column o, final ColumnModel f) {
+        token = o.token;
+        field = f;
+      }
+
+      public ColumnModel getField() {
+        return field;
+      }
+
+      public Tree dupNode() {
+        return new Column(this, field);
+      }
+    }
+
+    private RelationModel relationModel;
+
+    public void displayRecognitionError(String[] tokenNames,
+                                        RecognitionException e) {
+        String hdr = getErrorHeader(e);
+        String msg = getErrorMessage(e, tokenNames);
+        throw new QueryParseInternalException(hdr + " " + msg);
+    }
+}
+
+@lexer::header {
+package com.google.gwtorm.schema;
+}
+@lexer::members {
+    public void displayRecognitionError(String[] tokenNames,
+                                        RecognitionException e) {
+        String hdr = getErrorHeader(e);
+        String msg = getErrorMessage(e, tokenNames);
+        throw new QueryParseInternalException(hdr + " " + msg);
+    }
+}
+
+
+query
+  : where? orderBy? limit?
+  ;
+
+where
+  : WHERE^ conditions
+  ;
+
+orderBy
+  : ORDER^ BY! fieldSort (COMMA! fieldSort)*
+  ;
+
+fieldSort
+  : field sortDirection^
+  | field -> ^(ASC field)
+  ;
+
+sortDirection
+  : ASC
+  | DESC
+  ;
+
+limit
+  : LIMIT^ limitArg
+  ;
+
+limitArg
+  : PLACEHOLDER
+  | CONSTANT_INTEGER
+  ;
+
+conditions
+  : condition AND^ condition (AND! condition)*
+  | condition
+  ;
+
+condition
+  : field compare_op^ conditionValue
+  ;
+
+compare_op
+ : LT
+ | LE
+ | GT
+ | GE
+ | EQ
+ | NE
+ ;
+
+field
+  : ID -> ID<Column>[$ID, relationModel]
+  ;
+
+conditionValue
+  : PLACEHOLDER
+  | CONSTANT_INTEGER
+  | constantBoolean
+  | CONSTANT_STRING
+  ;
+
+constantBoolean
+  : TRUE
+  | FALSE
+  ;
+
+WHERE: 'WHERE' ;
+ORDER: 'ORDER' ;
+BY:    'BY'    ;
+AND:   'AND'   ;
+ASC:   'ASC'   ;
+DESC:  'DESC'  ;
+LIMIT: 'LIMIT' ;
+TRUE:  'true'  ;
+FALSE: 'false' ;
+
+LT : '<'  ;
+LE : '<=' ;
+GT : '>'  ;
+GE : '>=' ;
+EQ : '='  ;
+NE : '!=' ;
+
+PLACEHOLDER: '?' ;
+COMMA: ',' ;
+
+CONSTANT_INTEGER
+  : '0'
+  | '1'..'9' ('0'..'9')*
+  ;
+
+CONSTANT_STRING
+  : '\'' ( ~('\'') )* '\''
+  ;
+
+ID
+  : ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'_'|'0'..'9')*
+  ;
+
+WS
+  :  ( ' ' | '\r' | '\t' | '\n' ) { $channel=HIDDEN; }
+  ;
diff --git a/src/com/google/gwtorm/schema/QueryModel.java b/src/com/google/gwtorm/schema/QueryModel.java
new file mode 100644
index 0000000..c24bc2b
--- /dev/null
+++ b/src/com/google/gwtorm/schema/QueryModel.java
@@ -0,0 +1,319 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.schema;
+
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.Query;
+import com.google.gwtorm.schema.QueryParser.Column;
+import com.google.gwtorm.schema.sql.SqlBooleanTypeInfo;
+import com.google.gwtorm.schema.sql.SqlDialect;
+
+import org.antlr.runtime.CommonToken;
+import org.antlr.runtime.tree.CommonTree;
+import org.antlr.runtime.tree.Tree;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+public class QueryModel {
+  private final RelationModel model;
+  private final String name;
+  private final Query query;
+  private final Tree parsedQuery;
+
+  public QueryModel(final RelationModel rel, final String queryName,
+      final Query q) throws OrmException {
+    if (q == null) {
+      throw new OrmException("Query " + queryName + " is missing "
+          + Query.class.getName() + " annotation");
+    }
+
+    model = rel;
+    name = queryName;
+    query = q;
+
+    try {
+      parsedQuery = QueryParser.parse(model, q.value());
+    } catch (QueryParseException e) {
+      throw new OrmException("Cannot parse query " + q.value(), e);
+    }
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public Tree getParseTree() {
+    return parsedQuery;
+  }
+
+  public List<ColumnModel> getParameters() {
+    final ArrayList<ColumnModel> r = new ArrayList<ColumnModel>();
+    if (parsedQuery != null) {
+      findParameters(r, parsedQuery);
+    }
+    return r;
+  }
+
+  private void findParameters(final List<ColumnModel> r, final Tree node) {
+    switch (node.getType()) {
+      case QueryParser.WHERE:
+        extractParameters(r, node);
+        break;
+
+      default:
+        for (int i = 0; i < node.getChildCount(); i++) {
+          findParameters(r, node.getChild(i));
+        }
+        break;
+    }
+  }
+
+  private void extractParameters(final List<ColumnModel> r, final Tree node) {
+    switch (node.getType()) {
+      case QueryParser.LT:
+      case QueryParser.LE:
+      case QueryParser.GT:
+      case QueryParser.GE:
+      case QueryParser.EQ:
+      case QueryParser.NE:
+        if (node.getChild(1).getType() == QueryParser.PLACEHOLDER) {
+          r.add(((QueryParser.Column) node.getChild(0)).getField());
+        }
+        break;
+
+      default:
+        for (int i = 0; i < node.getChildCount(); i++) {
+          extractParameters(r, node.getChild(i));
+        }
+        break;
+    }
+  }
+
+  public boolean hasLimit() {
+    return findLimit(parsedQuery) != null;
+  }
+
+  public boolean hasLimitParameter() {
+    final Tree limit = findLimit(parsedQuery);
+    return limit != null
+        && limit.getChild(0).getType() == QueryParser.PLACEHOLDER;
+  }
+
+  public int getStaticLimit() {
+    return Integer.parseInt(findLimit(parsedQuery).getChild(0).getText());
+  }
+
+  private Tree findLimit(final Tree node) {
+    if (node == null) {
+      return null;
+    }
+    switch (node.getType()) {
+      case QueryParser.LIMIT:
+        return node;
+      default:
+        for (int i = 0; i < node.getChildCount(); i++) {
+          final Tree r = findLimit(node.getChild(i));
+          if (r != null) {
+            return r;
+          }
+        }
+        return null;
+    }
+  }
+
+  public String getSelectSql(final SqlDialect dialect, final String tableAlias) {
+    final StringBuilder buf = new StringBuilder();
+    buf.append(model.getSelectSql(dialect, tableAlias));
+    if (parsedQuery != null) {
+      final FormatInfo fmt = new FormatInfo(buf, dialect, tableAlias);
+      final Tree t = expand(parsedQuery);
+      if (t.getType() == 0) {
+        formatChilden(fmt, t);
+      } else {
+        format(fmt, t);
+      }
+    }
+    return buf.toString();
+  }
+
+  private void formatChilden(final FormatInfo fmt, final Tree node) {
+    for (int i = 0; i < node.getChildCount(); i++) {
+      format(fmt, node.getChild(i));
+    }
+  }
+
+  private void format(final FormatInfo fmt, final Tree node) {
+    switch (node.getType()) {
+      case QueryParser.WHERE:
+        fmt.buf.append(" WHERE ");
+        formatChilden(fmt, node);
+        break;
+
+      case QueryParser.AND:
+        for (int i = 0; i < node.getChildCount(); i++) {
+          if (i > 0) {
+            fmt.buf.append(" AND ");
+          }
+          format(fmt, node.getChild(i));
+        }
+        break;
+
+      case QueryParser.LT:
+      case QueryParser.LE:
+      case QueryParser.GT:
+      case QueryParser.GE:
+      case QueryParser.EQ:
+        format(fmt, node.getChild(0));
+        fmt.buf.append(node.getText());
+        format(fmt, node.getChild(1));
+        break;
+      case QueryParser.NE:
+        format(fmt, node.getChild(0));
+        fmt.buf.append("<>");
+        format(fmt, node.getChild(1));
+        break;
+
+      case QueryParser.ID: {
+        final ColumnModel col = ((QueryParser.Column) node).getField();
+        if (!col.isSqlPrimitive()) {
+          throw new IllegalStateException("Unexpanded nested field");
+        }
+        fmt.buf.append(fmt.tableAlias);
+        fmt.buf.append('.');
+        fmt.buf.append(col.getColumnName());
+        break;
+      }
+
+      case QueryParser.PLACEHOLDER:
+        fmt.buf.append(fmt.dialect.getParameterPlaceHolder(fmt.nthParam++));
+        break;
+
+      case QueryParser.TRUE:
+        fmt.buf.append(((SqlBooleanTypeInfo) fmt.dialect
+            .getSqlTypeInfo(Boolean.TYPE)).getTrueLiteralValue());
+        break;
+
+      case QueryParser.FALSE:
+        fmt.buf.append(((SqlBooleanTypeInfo) fmt.dialect
+            .getSqlTypeInfo(Boolean.TYPE)).getFalseLiteralValue());
+        break;
+
+      case QueryParser.CONSTANT_INTEGER:
+      case QueryParser.CONSTANT_STRING:
+        fmt.buf.append(node.getText());
+        break;
+
+      case QueryParser.ORDER:
+        fmt.buf.append(" ORDER BY ");
+        for (int i = 0; i < node.getChildCount(); i++) {
+          final Tree sortOrder = node.getChild(i);
+          final Tree id = sortOrder.getChild(0);
+          if (i > 0) {
+            fmt.buf.append(',');
+          }
+          final ColumnModel col = ((QueryParser.Column) id).getField();
+          if (col.isNested()) {
+            for (final Iterator<ColumnModel> cItr =
+                col.getAllLeafColumns().iterator(); cItr.hasNext();) {
+              fmt.buf.append(fmt.tableAlias);
+              fmt.buf.append('.');
+              fmt.buf.append(cItr.next().getColumnName());
+              if (sortOrder.getType() == QueryParser.DESC) {
+                fmt.buf.append(" DESC");
+              }
+              if (cItr.hasNext()) {
+                fmt.buf.append(',');
+              }
+            }
+          } else {
+            fmt.buf.append(fmt.tableAlias);
+            fmt.buf.append('.');
+            fmt.buf.append(col.getColumnName());
+            if (sortOrder.getType() == QueryParser.DESC) {
+              fmt.buf.append(" DESC");
+            }
+          }
+        }
+        break;
+
+      case QueryParser.LIMIT:
+        if (fmt.dialect.selectHasLimit()) {
+          final Tree p = node.getChild(0);
+          if (p.getType() == QueryParser.CONSTANT_INTEGER) {
+            fmt.buf.append(" LIMIT ");
+            fmt.buf.append(p.getText());
+          }
+        }
+        break;
+
+      default:
+        throw new IllegalStateException("Unsupported query token");
+    }
+  }
+
+  @Override
+  public String toString() {
+    return "Query[" + name + " " + query.value() + "]";
+  }
+
+  private Tree expand(final Tree node) {
+    switch (node.getType()) {
+      case QueryParser.LT:
+      case QueryParser.LE:
+      case QueryParser.GT:
+      case QueryParser.GE:
+      case QueryParser.EQ:
+      case QueryParser.NE: {
+        final Column qpc = (QueryParser.Column) node.getChild(0);
+        final ColumnModel f = qpc.getField();
+        if (f.isNested()) {
+          final CommonTree join;
+
+          join = new CommonTree(new CommonToken(QueryParser.AND));
+          for (final ColumnModel c : f.getAllLeafColumns()) {
+            final Tree op;
+
+            op = node.dupNode();
+            op.addChild(new QueryParser.Column(qpc, c));
+            op.addChild(node.getChild(1).dupNode());
+            join.addChild(op);
+          }
+          return join;
+        }
+      }
+    }
+
+    final Tree r = node.dupNode();
+    for (int i = 0; i < node.getChildCount(); i++) {
+      r.addChild(expand(node.getChild(i)));
+    }
+    return r;
+  }
+
+  static class FormatInfo {
+    final StringBuilder buf;
+    final SqlDialect dialect;
+    final String tableAlias;
+    int nthParam = 1;
+
+    FormatInfo(StringBuilder r, SqlDialect dialect, String tableAlias) {
+      this.buf = r;
+      this.dialect = dialect;
+      this.tableAlias = tableAlias;
+    }
+  }
+}
diff --git a/src/com/google/gwtorm/schema/QueryParseException.java b/src/com/google/gwtorm/schema/QueryParseException.java
new file mode 100644
index 0000000..db71fa0
--- /dev/null
+++ b/src/com/google/gwtorm/schema/QueryParseException.java
@@ -0,0 +1,21 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.schema;
+
+public class QueryParseException extends Exception {
+  public QueryParseException(final String message) {
+    super(message);
+  }
+}
diff --git a/src/com/google/gwtorm/schema/QueryParseInternalException.java b/src/com/google/gwtorm/schema/QueryParseInternalException.java
new file mode 100644
index 0000000..262f779
--- /dev/null
+++ b/src/com/google/gwtorm/schema/QueryParseInternalException.java
@@ -0,0 +1,21 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.schema;
+
+class QueryParseInternalException extends RuntimeException {
+  QueryParseInternalException(String message) {
+    super(message);
+  }
+}
diff --git a/src/com/google/gwtorm/schema/RelationModel.java b/src/com/google/gwtorm/schema/RelationModel.java
new file mode 100644
index 0000000..6757dc6
--- /dev/null
+++ b/src/com/google/gwtorm/schema/RelationModel.java
@@ -0,0 +1,305 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.schema;
+
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.PrimaryKey;
+import com.google.gwtorm.client.Relation;
+import com.google.gwtorm.schema.sql.SqlDialect;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+
+public abstract class RelationModel {
+  protected String methodName;
+  protected String relationName;
+  protected Relation relation;
+  protected final LinkedHashMap<String, ColumnModel> fieldsByFieldName;
+  protected final LinkedHashMap<String, ColumnModel> columnsByColumnName;
+  protected KeyModel primaryKey;
+  protected Collection<QueryModel> queries;
+
+  protected RelationModel() {
+    fieldsByFieldName = new LinkedHashMap<String, ColumnModel>();
+    columnsByColumnName = new LinkedHashMap<String, ColumnModel>();
+    queries = new ArrayList<QueryModel>();
+  }
+
+  protected void initName(final String method, final Relation rel)
+      throws OrmException {
+    if (rel == null) {
+      throw new OrmException("Method " + method + " is missing "
+          + Relation.class.getName() + " annotation");
+    }
+    relation = rel;
+    methodName = method;
+    relationName = Util.any(relation.name(), methodName);
+  }
+
+  protected void initColumns(final Collection<? extends ColumnModel> allFields)
+      throws OrmException {
+    for (final ColumnModel field : allFields) {
+      if (fieldsByFieldName.put(field.getFieldName(), field) != null) {
+        throw new OrmException("Duplicate fields " + field.getFieldName());
+      }
+
+      if (field.isNested()) {
+        for (final ColumnModel newCol : field.getAllLeafColumns()) {
+          registerColumn(newCol);
+        }
+      } else {
+        registerColumn(field);
+      }
+    }
+  }
+
+  private void registerColumn(final ColumnModel nc) throws OrmException {
+    final ColumnModel oc = columnsByColumnName.put(nc.getColumnName(), nc);
+    if (oc != null) {
+      throw new OrmException("Duplicate columns " + nc.getColumnName() + " in "
+          + getMethodName() + ":\n" + "prior " + oc.getPathToFieldName()
+          + "\n next  " + nc.getPathToFieldName());
+    }
+  }
+
+  protected void initPrimaryKey(final String name, final PrimaryKey annotation)
+      throws OrmException {
+    if (primaryKey != null) {
+      throw new OrmException("Duplicate primary key definitions");
+    }
+
+    final ColumnModel field = getField(annotation.value());
+    if (field == null) {
+      throw new OrmException("Field " + annotation.value() + " not in "
+          + getEntityTypeClassName());
+    }
+
+    primaryKey = new KeyModel(name, field);
+    for (final ColumnModel c : primaryKey.getAllLeafColumns()) {
+      c.inPrimaryKey = true;
+    }
+  }
+
+  protected void addQuery(final QueryModel q) {
+    queries.add(q);
+  }
+
+
+  public String getMethodName() {
+    return methodName;
+  }
+
+  public String getRelationName() {
+    return relationName;
+  }
+
+  public Collection<ColumnModel> getDependentFields() {
+    final ArrayList<ColumnModel> r = new ArrayList<ColumnModel>();
+    for (final ColumnModel c : fieldsByFieldName.values()) {
+      if (primaryKey == null || primaryKey.getField() != c) {
+        r.add(c);
+      }
+    }
+    return r;
+  }
+
+  public Collection<ColumnModel> getDependentColumns() {
+    final ArrayList<ColumnModel> r = new ArrayList<ColumnModel>();
+    for (final ColumnModel c : columnsByColumnName.values()) {
+      if (!c.inPrimaryKey) {
+        r.add(c);
+      }
+    }
+    return r;
+  }
+
+  public KeyModel getPrimaryKey() {
+    return primaryKey;
+  }
+
+  public Collection<ColumnModel> getPrimaryKeyColumns() {
+    if (getPrimaryKey() != null) {
+      return getPrimaryKey().getAllLeafColumns();
+    }
+    return Collections.<ColumnModel> emptyList();
+  }
+
+  public Collection<QueryModel> getQueries() {
+    return queries;
+  }
+
+  public Collection<ColumnModel> getColumns() {
+    final ArrayList<ColumnModel> r = new ArrayList<ColumnModel>();
+    r.addAll(getDependentColumns());
+    r.addAll(getPrimaryKeyColumns());
+    return r;
+  }
+
+  public Collection<ColumnModel> getFields() {
+    return fieldsByFieldName.values();
+  }
+
+  public ColumnModel getField(final String name) {
+    return fieldsByFieldName.get(name);
+  }
+
+  public String getCreateTableSql(final SqlDialect dialect) {
+    final StringBuilder r = new StringBuilder();
+    r.append("CREATE TABLE ");
+    r.append(relationName);
+    r.append(" (\n");
+
+    for (final Iterator<ColumnModel> i = getColumns().iterator(); i.hasNext();) {
+      final ColumnModel col = i.next();
+      r.append(col.getColumnName());
+      r.append(" ");
+      r.append(dialect.getSqlTypeInfo(col).getSqlType(col));
+      if (i.hasNext()) {
+        r.append(",");
+      } else if (!getPrimaryKeyColumns().isEmpty()) {
+        r.append(",");
+      }
+      r.append("\n");
+    }
+
+    if (!getPrimaryKeyColumns().isEmpty()) {
+      r.append("PRIMARY KEY(");
+      for (final Iterator<ColumnModel> i = getPrimaryKeyColumns().iterator(); i
+          .hasNext();) {
+        final ColumnModel col = i.next();
+        r.append(col.getColumnName());
+        if (i.hasNext()) {
+          r.append(",");
+        }
+      }
+      r.append(")\n");
+    }
+
+    r.append(")");
+    return r.toString();
+  }
+
+  public String getSelectSql(final SqlDialect dialect, final String tableAlias) {
+    final StringBuilder r = new StringBuilder();
+    r.append("SELECT ");
+    for (final Iterator<ColumnModel> i = getColumns().iterator(); i.hasNext();) {
+      final ColumnModel col = i.next();
+      r.append(tableAlias);
+      r.append('.');
+      r.append(col.getColumnName());
+      if (i.hasNext()) {
+        r.append(",");
+      }
+    }
+    r.append(" FROM ");
+    r.append(relationName);
+    r.append(' ');
+    r.append(tableAlias);
+    return r.toString();
+  }
+
+  public String getInsertOneSql(final SqlDialect dialect) {
+    final StringBuilder r = new StringBuilder();
+    r.append("INSERT INTO ");
+    r.append(relationName);
+    r.append("(");
+    for (final Iterator<ColumnModel> i = getColumns().iterator(); i.hasNext();) {
+      final ColumnModel col = i.next();
+      r.append(col.getColumnName());
+      if (i.hasNext()) {
+        r.append(",");
+      }
+    }
+    r.append(")VALUES(");
+    int nth = 1;
+    for (final Iterator<ColumnModel> i = getColumns().iterator(); i.hasNext();) {
+      i.next();
+      r.append(dialect.getParameterPlaceHolder(nth++));
+      if (i.hasNext()) {
+        r.append(",");
+      }
+    }
+    r.append(")");
+    return r.toString();
+  }
+
+  public String getUpdateOneSql(final SqlDialect dialect) {
+    final StringBuilder r = new StringBuilder();
+    r.append("UPDATE ");
+    r.append(relationName);
+    r.append(" SET ");
+    int nth = 1;
+    for (final Iterator<ColumnModel> i = getDependentColumns().iterator(); i
+        .hasNext();) {
+      final ColumnModel col = i.next();
+      r.append(col.getColumnName());
+      r.append("=");
+      r.append(dialect.getParameterPlaceHolder(nth++));
+      if (i.hasNext()) {
+        r.append(",");
+      }
+    }
+    r.append(" WHERE ");
+    for (final Iterator<ColumnModel> i = getPrimaryKeyColumns().iterator(); i
+        .hasNext();) {
+      final ColumnModel col = i.next();
+      r.append(col.getColumnName());
+      r.append("=");
+      r.append(dialect.getParameterPlaceHolder(nth++));
+      if (i.hasNext()) {
+        r.append(" AND ");
+      }
+    }
+    return r.toString();
+  }
+
+  public String getDeleteOneSql(final SqlDialect dialect) {
+    final StringBuilder r = new StringBuilder();
+    r.append("DELETE FROM ");
+    r.append(relationName);
+    int nth = 1;
+    r.append(" WHERE ");
+    for (final Iterator<ColumnModel> i = getPrimaryKeyColumns().iterator(); i
+        .hasNext();) {
+      final ColumnModel col = i.next();
+      r.append(col.getColumnName());
+      r.append("=");
+      r.append(dialect.getParameterPlaceHolder(nth++));
+      if (i.hasNext()) {
+        r.append(" AND ");
+      }
+    }
+    return r.toString();
+  }
+
+  public abstract String getAccessInterfaceName();
+
+  public abstract String getEntityTypeClassName();
+
+  @Override
+  public String toString() {
+    final StringBuilder r = new StringBuilder();
+    r.append("Relation[\n");
+    r.append("  method: " + getMethodName() + "\n");
+    r.append("  table:  " + getRelationName() + "\n");
+    r.append("  access: " + getAccessInterfaceName() + "\n");
+    r.append("  entity: " + getEntityTypeClassName() + "\n");
+    r.append("]");
+    return r.toString();
+  }
+}
diff --git a/src/com/google/gwtorm/schema/SchemaModel.java b/src/com/google/gwtorm/schema/SchemaModel.java
new file mode 100644
index 0000000..39146a7
--- /dev/null
+++ b/src/com/google/gwtorm/schema/SchemaModel.java
@@ -0,0 +1,95 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.schema;
+
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.schema.sql.SqlDialect;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+
+public abstract class SchemaModel {
+  protected final Set<String> allNames;
+  protected final Map<String, RelationModel> relations;
+  protected final Map<String, SequenceModel> sequences;
+
+  protected SchemaModel() {
+    allNames = new HashSet<String>();
+    relations = new LinkedHashMap<String, RelationModel>();
+    sequences = new LinkedHashMap<String, SequenceModel>();
+  }
+
+  protected void add(final RelationModel r) throws OrmException {
+    final String n = r.getRelationName();
+    checkNotUsed(n);
+    if (relations.put(n, r) != null) {
+      throw new OrmException("Duplicate relations " + n);
+    }
+    allNames.add(n);
+  }
+
+  protected void add(final SequenceModel s) throws OrmException {
+    final String n = s.getSequenceName();
+    checkNotUsed(n);
+    if (sequences.put(n, s) != null) {
+      throw new OrmException("Duplicate sequences " + n);
+    }
+    allNames.add(n);
+  }
+
+  private void checkNotUsed(final String n) throws OrmException {
+    if (allNames.contains(n)) {
+      throw new OrmException("Name " + n + " already used");
+    }
+  }
+
+  public Collection<RelationModel> getRelations() {
+    return relations.values();
+  }
+
+  public Collection<SequenceModel> getSequences() {
+    return sequences.values();
+  }
+
+  public String getCreateDatabaseSql(final SqlDialect dialect) {
+    final StringBuffer r = new StringBuffer();
+
+    for (final SequenceModel seq : getSequences()) {
+      r.append(seq.getCreateSequenceSql(dialect));
+      r.append(";\n");
+    }
+    if (!getSequences().isEmpty()) {
+      r.append("\n");
+    }
+
+    for (final RelationModel rel : getRelations()) {
+      r.append(rel.getCreateTableSql(dialect));
+      r.append(";\n\n");
+    }
+
+    return r.toString();
+  }
+
+  public abstract String getSchemaClassName();
+
+  @Override
+  public String toString() {
+    return "Schema[" + getSchemaClassName() + "]";
+  }
+}
diff --git a/src/com/google/gwtorm/schema/SequenceModel.java b/src/com/google/gwtorm/schema/SequenceModel.java
new file mode 100644
index 0000000..aa9e389
--- /dev/null
+++ b/src/com/google/gwtorm/schema/SequenceModel.java
@@ -0,0 +1,80 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.schema;
+
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.Sequence;
+import com.google.gwtorm.schema.sql.SqlDialect;
+
+public class SequenceModel {
+  protected String methodName;
+  protected String name;
+  protected Sequence sequence;
+  protected Class<?> returnType;
+
+  public SequenceModel(final String method, final Sequence seq,
+      final Class<?> type) throws OrmException {
+    if (seq == null) {
+      throw new OrmException("Method " + method + " is missing "
+          + Sequence.class.getName() + " annotation");
+    }
+    if (type != Integer.TYPE && type != Long.TYPE) {
+      throw new OrmException("Sequence method " + method
+          + " must return int or long");
+    }
+
+    sequence = seq;
+    methodName = method;
+
+    final String n;
+    if (methodName.startsWith("next")) {
+      n = methodName.substring(4).toLowerCase();
+    } else {
+      n = methodName;
+    }
+    name = Util.any(sequence.name(), n);
+    returnType = type;
+  }
+
+  public String getMethodName() {
+    return methodName;
+  }
+
+  public String getSequenceName() {
+    return name;
+  }
+
+  public Class<?> getResultType() {
+    return returnType;
+  }
+
+  public Sequence getSequence() {
+    return sequence;
+  }
+
+  public String getCreateSequenceSql(final SqlDialect dialect) {
+    return dialect.getCreateSequenceSql(this);
+  }
+
+  @Override
+  public String toString() {
+    final StringBuilder r = new StringBuilder();
+    r.append("Sequence[\n");
+    r.append("  method: " + getMethodName() + "\n");
+    r.append("  name:   " + getSequenceName() + "\n");
+    r.append("]");
+    return r.toString();
+  }
+}
diff --git a/src/com/google/gwtorm/schema/Util.java b/src/com/google/gwtorm/schema/Util.java
new file mode 100644
index 0000000..2a90f28
--- /dev/null
+++ b/src/com/google/gwtorm/schema/Util.java
@@ -0,0 +1,59 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.schema;
+
+
+public class Util {
+  private static int nameCounter;
+
+  public static synchronized String createRandomName() {
+    return "GwtOrm$$" + nameCounter++;
+  }
+
+  public static String any(final String a, final String b) {
+    if (a != null && a.length() > 0) {
+      return a;
+    }
+    return b;
+  }
+
+  public static boolean samePackage(final String aName, final String bName) {
+    return packageOf(aName).equals(packageOf(bName));
+  }
+
+  public static String packageOf(final String className) {
+    final int end = className.lastIndexOf('.');
+    if (end < 0) {
+      return "";
+    }
+    return className.substring(0, end);
+  }
+
+  public static boolean isSqlPrimitive(final Class<?> type) {
+    if (type == null || type == Void.TYPE) {
+      return false;
+    }
+    if (type.isPrimitive()) {
+      return true;
+    }
+    if (type == String.class) {
+      return true;
+    }
+    return false;
+  }
+
+  private Util() {
+  }
+}
diff --git a/src/com/google/gwtorm/schema/gwt/GwtColumnModel.java b/src/com/google/gwtorm/schema/gwt/GwtColumnModel.java
new file mode 100644
index 0000000..ddbf324
--- /dev/null
+++ b/src/com/google/gwtorm/schema/gwt/GwtColumnModel.java
@@ -0,0 +1,127 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.schema.gwt;
+
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JField;
+import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
+import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwtorm.client.Column;
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.schema.ColumnModel;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+class GwtColumnModel extends ColumnModel {
+  static Class<?> toClass(final JType type) {
+    if (type.isPrimitive() == JPrimitiveType.BOOLEAN) {
+      return Boolean.TYPE;
+    }
+
+    if (type.isPrimitive() == JPrimitiveType.BYTE) {
+      return Byte.TYPE;
+    }
+
+    if (type.isPrimitive() == JPrimitiveType.SHORT) {
+      return Short.TYPE;
+    }
+
+    if (type.isPrimitive() == JPrimitiveType.CHAR) {
+      return Character.TYPE;
+    }
+
+    if (type.isPrimitive() == JPrimitiveType.INT) {
+      return Integer.TYPE;
+    }
+
+    if (type.isPrimitive() == JPrimitiveType.LONG) {
+      return Long.TYPE;
+    }
+
+    if (type.isPrimitive() == JPrimitiveType.FLOAT) {
+      return Float.TYPE;
+    }
+
+    if (type.isPrimitive() == JPrimitiveType.DOUBLE) {
+      return Double.TYPE;
+    }
+
+    if (type.isClass() != null
+        && type.getQualifiedSourceName().startsWith("java.")) {
+      try {
+        return Class.forName(type.getQualifiedSourceName());
+      } catch (ClassNotFoundException err) {
+        // Never happen, but fall through anyway
+      }
+    }
+
+    return null;
+  }
+
+  private final JField field;
+  private final Class<?> primitiveType;
+
+  GwtColumnModel(final JField columnField) throws OrmException {
+    field = columnField;
+    initName(field.getName(), field.getAnnotation(Column.class));
+
+    if (field.isPrivate()) {
+      throw new OrmException("Field " + getFieldName() + " of "
+          + field.getEnclosingType().getQualifiedSourceName()
+          + " must not be private");
+    }
+    if (field.isFinal()) {
+      throw new OrmException("Field " + getFieldName() + " of "
+          + field.getEnclosingType().getQualifiedSourceName()
+          + " must not be final");
+    }
+
+    primitiveType = toClass(field.getType());
+
+    if (isNested()) {
+      final List<GwtColumnModel> col = new ArrayList<GwtColumnModel>();
+      JClassType in = field.getType().isClass();
+      while (in != null) {
+        for (final JField f : in.getFields()) {
+          if (f.getAnnotation(Column.class) != null) {
+            col.add(new GwtColumnModel(f));
+          }
+        }
+        in = in.getSuperclass();
+      }
+      initNestedColumns(col);
+    }
+  }
+
+  @Override
+  public String getFieldName() {
+    return field.getName();
+  }
+
+  @Override
+  public Class<?> getPrimitiveType() {
+    return primitiveType;
+  }
+
+  @Override
+  public String getNestedClassName() {
+    if (primitiveType == null) {
+      return field.getType().getQualifiedSourceName();
+    }
+    return null;
+  }
+}
diff --git a/src/com/google/gwtorm/schema/gwt/GwtRelationModel.java b/src/com/google/gwtorm/schema/gwt/GwtRelationModel.java
new file mode 100644
index 0000000..b0ef6ec
--- /dev/null
+++ b/src/com/google/gwtorm/schema/gwt/GwtRelationModel.java
@@ -0,0 +1,108 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.schema.gwt;
+
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JField;
+import com.google.gwt.core.ext.typeinfo.JGenericType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwtorm.client.Access;
+import com.google.gwtorm.client.Column;
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.PrimaryKey;
+import com.google.gwtorm.client.Query;
+import com.google.gwtorm.client.Relation;
+import com.google.gwtorm.schema.QueryModel;
+import com.google.gwtorm.schema.RelationModel;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class GwtRelationModel extends RelationModel {
+  private final JMethod method;
+  private final JClassType accessType;
+  private final JClassType entityType;
+
+  GwtRelationModel(final JMethod m) throws OrmException {
+    method = m;
+    initName(method.getName(), m.getAnnotation(Relation.class));
+
+    accessType = method.getReturnType().isInterface();
+    if (accessType == null) {
+      throw new OrmException("Method " + method.getName() + " in "
+          + method.getEnclosingType().getQualifiedSourceName()
+          + " must return an extension of " + Access.class);
+    }
+
+    if (accessType.getImplementedInterfaces().length != 1
+        || !accessType.getImplementedInterfaces()[0].getQualifiedSourceName()
+            .equals(Access.class.getName())) {
+      throw new OrmException("Method " + method.getName() + " in "
+          + method.getEnclosingType().getQualifiedSourceName()
+          + " must return a direct extension of " + Access.class);
+    }
+
+    final JGenericType gt =
+        accessType.getImplementedInterfaces()[0].isGenericType();
+    if (gt == null) {
+      throw new OrmException(accessType.getQualifiedSourceName()
+          + " must specify entity type parameter for " + Access.class);
+    }
+    entityType = gt.getTypeParameters()[0];
+
+    initColumns();
+    initQueriesAndKeys();
+  }
+
+  private void initColumns() throws OrmException {
+    final List<GwtColumnModel> col = new ArrayList<GwtColumnModel>();
+    JClassType in = entityType;
+    while (in != null) {
+      for (final JField f : in.getFields()) {
+        if (f.getAnnotation(Column.class) != null) {
+          col.add(new GwtColumnModel(f));
+        }
+      }
+      in = in.getSuperclass();
+    }
+    initColumns(col);
+  }
+
+  private void initQueriesAndKeys() throws OrmException {
+    for (final JMethod m : accessType.getMethods()) {
+      if (m.getAnnotation(PrimaryKey.class) != null) {
+        if (!m.getReturnType().getQualifiedSourceName().equals(
+            entityType.getQualifiedSourceName())) {
+          throw new OrmException("PrimaryKey " + m.getName() + " must return "
+              + entityType.getName());
+        }
+        initPrimaryKey(m.getName(), m.getAnnotation(PrimaryKey.class));
+
+      } else if (m.getAnnotation(Query.class) != null) {
+        addQuery(new QueryModel(this, m.getName(), m.getAnnotation(Query.class)));
+      }
+    }
+  }
+
+  @Override
+  public String getAccessInterfaceName() {
+    return accessType.getQualifiedSourceName();
+  }
+
+  @Override
+  public String getEntityTypeClassName() {
+    return entityType.getQualifiedSourceName();
+  }
+}
diff --git a/src/com/google/gwtorm/schema/gwt/GwtSchemaModel.java b/src/com/google/gwtorm/schema/gwt/GwtSchemaModel.java
new file mode 100644
index 0000000..e804083
--- /dev/null
+++ b/src/com/google/gwtorm/schema/gwt/GwtSchemaModel.java
@@ -0,0 +1,73 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.schema.gwt;
+
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
+import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.Relation;
+import com.google.gwtorm.client.Schema;
+import com.google.gwtorm.client.Sequence;
+import com.google.gwtorm.schema.SchemaModel;
+import com.google.gwtorm.schema.SequenceModel;
+
+public class GwtSchemaModel extends SchemaModel {
+  private final JClassType schema;
+
+  public GwtSchemaModel(final JClassType schemaInterface) throws OrmException {
+    schema = schemaInterface;
+
+    if (schema.isInterface() == null) {
+      throw new OrmException("Schema " + schema.getName()
+          + " must be an interface");
+    }
+
+    if (schema.getImplementedInterfaces().length != 1
+        || !schema.getImplementedInterfaces()[0].getQualifiedSourceName()
+            .equals(Schema.class.getName())) {
+      throw new OrmException("Schema " + schema.getName()
+          + " must only extend " + Schema.class.getName());
+    }
+
+    for (final JMethod m : schema.getMethods()) {
+      if (m.getAnnotation(Relation.class) != null) {
+        add(new GwtRelationModel(m));
+        continue;
+      }
+
+      final Sequence seq = m.getAnnotation(Sequence.class);
+      if (seq != null) {
+        final JType returnType = m.getReturnType();
+        final Class<?> r;
+        if (returnType == JPrimitiveType.INT) {
+          r = Integer.TYPE;
+        } else if (returnType == JPrimitiveType.LONG) {
+          r = Long.TYPE;
+        } else {
+          r = Object.class;
+        }
+        add(new SequenceModel(m.getName(), seq, r));
+        continue;
+      }
+    }
+  }
+
+  @Override
+  public String getSchemaClassName() {
+    return schema.getQualifiedSourceName();
+  }
+}
diff --git a/src/com/google/gwtorm/schema/java/JavaColumnModel.java b/src/com/google/gwtorm/schema/java/JavaColumnModel.java
new file mode 100644
index 0000000..47c79f5
--- /dev/null
+++ b/src/com/google/gwtorm/schema/java/JavaColumnModel.java
@@ -0,0 +1,77 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.schema.java;
+
+import com.google.gwtorm.client.Column;
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.schema.ColumnModel;
+import com.google.gwtorm.schema.Util;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+
+
+class JavaColumnModel extends ColumnModel {
+  private final Field field;
+
+  JavaColumnModel(final Field columnField) throws OrmException {
+    field = columnField;
+    initName(field.getName(), field.getAnnotation(Column.class));
+
+    if (Modifier.isPrivate(field.getModifiers())) {
+      throw new OrmException("Field " + field.getName() + " of "
+          + field.getDeclaringClass().getName() + " must not be private");
+    }
+    if (Modifier.isFinal(field.getModifiers())) {
+      throw new OrmException("Field " + field.getName() + " of "
+          + field.getDeclaringClass().getName() + " must not be final");
+    }
+
+    if (isNested()) {
+      final List<JavaColumnModel> col = new ArrayList<JavaColumnModel>();
+      Class<?> in = field.getType();
+      while (in != null) {
+        for (final Field f : in.getDeclaredFields()) {
+          if (f.getAnnotation(Column.class) != null) {
+            col.add(new JavaColumnModel(f));
+          }
+        }
+        in = in.getSuperclass();
+      }
+      initNestedColumns(col);
+    }
+  }
+
+  @Override
+  public String getFieldName() {
+    return field.getName();
+  }
+
+  @Override
+  public Class<?> getPrimitiveType() {
+    return isPrimitive() ? field.getType() : null;
+  }
+
+  @Override
+  public String getNestedClassName() {
+    return isPrimitive() ? null : field.getType().getName();
+  }
+
+  private boolean isPrimitive() {
+    return Util.isSqlPrimitive(field.getType());
+  }
+}
diff --git a/src/com/google/gwtorm/schema/java/JavaRelationModel.java b/src/com/google/gwtorm/schema/java/JavaRelationModel.java
new file mode 100644
index 0000000..f447095
--- /dev/null
+++ b/src/com/google/gwtorm/schema/java/JavaRelationModel.java
@@ -0,0 +1,109 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.schema.java;
+
+import com.google.gwtorm.client.Access;
+import com.google.gwtorm.client.Column;
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.PrimaryKey;
+import com.google.gwtorm.client.Query;
+import com.google.gwtorm.client.Relation;
+import com.google.gwtorm.client.ResultSet;
+import com.google.gwtorm.schema.QueryModel;
+import com.google.gwtorm.schema.RelationModel;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+
+class JavaRelationModel extends RelationModel {
+  private final Method method;
+  private final Class<?> accessType;
+  private final Class<?> entityType;
+
+  JavaRelationModel(final Method m) throws OrmException {
+    method = m;
+    initName(method.getName(), m.getAnnotation(Relation.class));
+
+    accessType = method.getReturnType();
+    if (accessType.getInterfaces().length != 1
+        || accessType.getInterfaces()[0] != Access.class) {
+      throw new OrmException("Method " + method.getName() + " in "
+          + method.getDeclaringClass().getName()
+          + " must return a direct extension of " + Access.class);
+    }
+
+    final Type gt = accessType.getGenericInterfaces()[0];
+    if (!(gt instanceof ParameterizedType)) {
+      throw new OrmException(accessType.getName()
+          + " must specify entity type parameter for " + Access.class);
+    }
+
+    entityType =
+        (Class<?>) ((ParameterizedType) gt).getActualTypeArguments()[0];
+
+    initColumns();
+    initQueriesAndKeys();
+  }
+
+  private void initColumns() throws OrmException {
+    final List<JavaColumnModel> col = new ArrayList<JavaColumnModel>();
+    Class<?> in = entityType;
+    while (in != null) {
+      for (final Field f : in.getDeclaredFields()) {
+        if (f.getAnnotation(Column.class) != null) {
+          col.add(new JavaColumnModel(f));
+        }
+      }
+      in = in.getSuperclass();
+    }
+    initColumns(col);
+  }
+
+  private void initQueriesAndKeys() throws OrmException {
+    for (final Method m : accessType.getDeclaredMethods()) {
+      if (m.getAnnotation(PrimaryKey.class) != null) {
+        if (m.getReturnType() != entityType) {
+          throw new OrmException("PrimaryKey " + m.getName() + " must return "
+              + entityType.getName());
+        }
+        initPrimaryKey(m.getName(), m.getAnnotation(PrimaryKey.class));
+
+      } else if (m.getAnnotation(Query.class) != null) {
+        if (!ResultSet.class.isAssignableFrom(m.getReturnType())
+            || !(m.getGenericReturnType() instanceof ParameterizedType)
+            || ((ParameterizedType) m.getGenericReturnType())
+                .getActualTypeArguments()[0] != entityType) {
+          throw new OrmException("Query " + m.getName() + " must return"
+              + " ResultSet<" + entityType.getName() + ">");
+        }
+        addQuery(new QueryModel(this, m.getName(), m.getAnnotation(Query.class)));
+      }
+    }
+  }
+
+  @Override
+  public String getAccessInterfaceName() {
+    return accessType.getName();
+  }
+
+  @Override
+  public String getEntityTypeClassName() {
+    return entityType.getName();
+  }
+}
diff --git a/src/com/google/gwtorm/schema/java/JavaSchemaModel.java b/src/com/google/gwtorm/schema/java/JavaSchemaModel.java
new file mode 100644
index 0000000..53eabc9
--- /dev/null
+++ b/src/com/google/gwtorm/schema/java/JavaSchemaModel.java
@@ -0,0 +1,62 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.schema.java;
+
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.Relation;
+import com.google.gwtorm.client.Schema;
+import com.google.gwtorm.client.Sequence;
+import com.google.gwtorm.schema.SchemaModel;
+import com.google.gwtorm.schema.SequenceModel;
+
+import java.lang.reflect.Method;
+
+
+public class JavaSchemaModel extends SchemaModel {
+  private final Class<?> schema;
+
+  public JavaSchemaModel(final Class<?> schemaInterface) throws OrmException {
+    schema = schemaInterface;
+
+    if (!schema.isInterface()) {
+      throw new OrmException("Schema " + schema.getName()
+          + " must be an interface");
+    }
+
+    if (schema.getInterfaces().length != 1
+        || schema.getInterfaces()[0] != Schema.class) {
+      throw new OrmException("Schema " + schema.getName()
+          + " must only extend " + Schema.class.getName());
+    }
+
+    for (final Method m : schema.getDeclaredMethods()) {
+      if (m.getAnnotation(Relation.class) != null) {
+        add(new JavaRelationModel(m));
+        continue;
+      }
+
+      final Sequence seq = m.getAnnotation(Sequence.class);
+      if (seq != null) {
+        add(new SequenceModel(m.getName(), seq, m.getReturnType()));
+        continue;
+      }
+    }
+  }
+
+  @Override
+  public String getSchemaClassName() {
+    return schema.getName();
+  }
+}
diff --git a/src/com/google/gwtorm/schema/sql/DialectH2.java b/src/com/google/gwtorm/schema/sql/DialectH2.java
new file mode 100644
index 0000000..adf6be2
--- /dev/null
+++ b/src/com/google/gwtorm/schema/sql/DialectH2.java
@@ -0,0 +1,9 @@
+package com.google.gwtorm.schema.sql;
+
+/** Dialect for <a href="http://www.h2database.com/">H2</a> */
+public class DialectH2 extends SqlDialect {
+  @Override
+  public String getNextSequenceValueSql(final String seqname) {
+    return "SELECT NEXT VALUE FOR " + seqname;
+  }
+}
diff --git a/src/com/google/gwtorm/schema/sql/DialectPostgreSQL.java b/src/com/google/gwtorm/schema/sql/DialectPostgreSQL.java
new file mode 100644
index 0000000..eed004b
--- /dev/null
+++ b/src/com/google/gwtorm/schema/sql/DialectPostgreSQL.java
@@ -0,0 +1,9 @@
+package com.google.gwtorm.schema.sql;
+
+/** Dialect for <a href="http://www.postgresql.org/>PostgreSQL</a> */
+public class DialectPostgreSQL extends SqlDialect {
+  @Override
+  public String getNextSequenceValueSql(final String seqname) {
+    return "SELECT nextval('" + seqname + "')";
+  }
+}
diff --git a/src/com/google/gwtorm/schema/sql/SqlBooleanTypeInfo.java b/src/com/google/gwtorm/schema/sql/SqlBooleanTypeInfo.java
new file mode 100644
index 0000000..fae2f1b
--- /dev/null
+++ b/src/com/google/gwtorm/schema/sql/SqlBooleanTypeInfo.java
@@ -0,0 +1,91 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.schema.sql;
+
+import com.google.gwtorm.jdbc.gen.CodeGenSupport;
+import com.google.gwtorm.schema.ColumnModel;
+
+import org.objectweb.asm.Label;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.sql.Types;
+
+public class SqlBooleanTypeInfo extends SqlTypeInfo {
+  @Override
+  public String getSqlType(final ColumnModel column) {
+    final String name = column.getColumnName();
+    final String t = getTrueLiteralValue();
+    final String f = getFalseLiteralValue();
+    return "CHAR(1) NOT NULL DEFAULT " + f + " CHECK (" + name + " IN (" + t
+        + "," + f + "))";
+  }
+
+  @Override
+  protected String getJavaSqlTypeAlias() {
+    return "String";
+  }
+
+  @Override
+  protected int getSqlTypeConstant() {
+    return Types.CHAR;
+  }
+
+  public String getTrueValue() {
+    return "Y";
+  }
+
+  public String getFalseValue() {
+    return "N";
+  }
+
+  public String getTrueLiteralValue() {
+    return "'" + getTrueValue() + "'";
+  }
+
+  public String getFalseLiteralValue() {
+    return "'" + getFalseValue() + "'";
+  }
+
+  @Override
+  public void generatePreparedStatementSet(final CodeGenSupport cgs) {
+    cgs.pushSqlHandle();
+    cgs.pushColumnIndex();
+    cgs.pushFieldValue();
+
+    final Label useNo = new Label();
+    final Label end = new Label();
+    cgs.mv.visitJumpInsn(Opcodes.IFEQ, useNo);
+    cgs.mv.visitLdcInsn(getTrueValue());
+    cgs.mv.visitJumpInsn(Opcodes.GOTO, end);
+    cgs.mv.visitLabel(useNo);
+    cgs.mv.visitLdcInsn(getFalseValue());
+    cgs.mv.visitLabel(end);
+    cgs.invokePreparedStatementSet(getJavaSqlTypeAlias());
+  }
+
+  @Override
+  public void generateResultSetGet(final CodeGenSupport cgs) {
+    cgs.fieldSetBegin();
+    cgs.mv.visitLdcInsn(getTrueValue());
+    cgs.pushSqlHandle();
+    cgs.pushColumnIndex();
+    cgs.invokeResultSetGet(getJavaSqlTypeAlias());
+    cgs.mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type
+        .getInternalName(String.class), "equals", Type.getMethodDescriptor(
+        Type.BOOLEAN_TYPE, new Type[] {Type.getType(Object.class)}));
+    cgs.fieldSetEnd();
+  }
+}
diff --git a/src/com/google/gwtorm/schema/sql/SqlDialect.java b/src/com/google/gwtorm/schema/sql/SqlDialect.java
new file mode 100644
index 0000000..5d42304
--- /dev/null
+++ b/src/com/google/gwtorm/schema/sql/SqlDialect.java
@@ -0,0 +1,72 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.schema.sql;
+
+import com.google.gwtorm.client.Sequence;
+import com.google.gwtorm.schema.ColumnModel;
+import com.google.gwtorm.schema.SequenceModel;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public abstract class SqlDialect {
+  protected final Map<Class<?>, SqlTypeInfo> types;
+
+  protected SqlDialect() {
+    types = new HashMap<Class<?>, SqlTypeInfo>();
+    types.put(Boolean.TYPE, new SqlBooleanTypeInfo());
+    types.put(Integer.TYPE, new SqlIntTypeInfo());
+    types.put(Long.TYPE, new SqlLongTypeInfo());
+    types.put(String.class, new SqlStringTypeInfo());
+    types.put(java.sql.Timestamp.class, new SqlTimestampTypeInfo());
+  }
+
+  public SqlTypeInfo getSqlTypeInfo(final ColumnModel col) {
+    return getSqlTypeInfo(col.getPrimitiveType());
+  }
+
+  public SqlTypeInfo getSqlTypeInfo(final Class<?> t) {
+    return types.get(t);
+  }
+
+  public String getParameterPlaceHolder(final int nthParameter) {
+    return "?";
+  }
+
+  public boolean selectHasLimit() {
+    return true;
+  }
+
+  public String getCreateSequenceSql(final SequenceModel seq) {
+    final Sequence s = seq.getSequence();
+    final StringBuilder r = new StringBuilder();
+    r.append("CREATE SEQUENCE ");
+    r.append(seq.getSequenceName());
+
+    if (s.startsWith() > 0) {
+      r.append(" STARTS WITH ");
+      r.append(s.startsWith());
+    }
+
+    if (s.cache() > 0) {
+      r.append(" CACHE ");
+      r.append(s.cache());
+    }
+
+    return r.toString();
+  }
+
+  public abstract String getNextSequenceValueSql(String seqname);
+}
diff --git a/src/com/google/gwtorm/schema/sql/SqlIntTypeInfo.java b/src/com/google/gwtorm/schema/sql/SqlIntTypeInfo.java
new file mode 100644
index 0000000..554c1c4
--- /dev/null
+++ b/src/com/google/gwtorm/schema/sql/SqlIntTypeInfo.java
@@ -0,0 +1,36 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.schema.sql;
+
+import com.google.gwtorm.schema.ColumnModel;
+
+import java.sql.Types;
+
+public class SqlIntTypeInfo extends SqlTypeInfo {
+  @Override
+  protected String getJavaSqlTypeAlias() {
+    return "Int";
+  }
+
+  @Override
+  protected int getSqlTypeConstant() {
+    return Types.INTEGER;
+  }
+
+  @Override
+  public String getSqlType(final ColumnModel column) {
+    return "INT DEFAULT 0 NOT NULL";
+  }
+}
diff --git a/src/com/google/gwtorm/schema/sql/SqlLongTypeInfo.java b/src/com/google/gwtorm/schema/sql/SqlLongTypeInfo.java
new file mode 100644
index 0000000..1b1f50c
--- /dev/null
+++ b/src/com/google/gwtorm/schema/sql/SqlLongTypeInfo.java
@@ -0,0 +1,36 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.schema.sql;
+
+import com.google.gwtorm.schema.ColumnModel;
+
+import java.sql.Types;
+
+public class SqlLongTypeInfo extends SqlTypeInfo {
+  @Override
+  protected String getJavaSqlTypeAlias() {
+    return "Long";
+  }
+
+  @Override
+  protected int getSqlTypeConstant() {
+    return Types.BIGINT;
+  }
+
+  @Override
+  public String getSqlType(final ColumnModel column) {
+    return "BIGINT DEFAULT 0 NOT NULL";
+  }
+}
diff --git a/src/com/google/gwtorm/schema/sql/SqlStringTypeInfo.java b/src/com/google/gwtorm/schema/sql/SqlStringTypeInfo.java
new file mode 100644
index 0000000..57de3af
--- /dev/null
+++ b/src/com/google/gwtorm/schema/sql/SqlStringTypeInfo.java
@@ -0,0 +1,51 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.schema.sql;
+
+import com.google.gwtorm.client.Column;
+import com.google.gwtorm.schema.ColumnModel;
+
+import java.sql.Types;
+
+public class SqlStringTypeInfo extends SqlTypeInfo {
+  @Override
+  protected String getJavaSqlTypeAlias() {
+    return "String";
+  }
+
+  @Override
+  protected int getSqlTypeConstant() {
+    return Types.VARCHAR;
+  }
+
+  @Override
+  public String getSqlType(final ColumnModel col) {
+    final Column column = col.getColumnAnnotation();
+    final StringBuilder r = new StringBuilder();
+
+    if (column.length() <= 0) {
+      r.append("VARCHAR(255)");
+    } else if (column.length() < 255) {
+      r.append("VARCHAR(" + column.length() + ")");
+    }
+
+    if (column.notNull()) {
+      r.append(" DEFAULT ''");
+      r.append(" NOT NULL");
+    }
+
+    return r.toString();
+  }
+}
diff --git a/src/com/google/gwtorm/schema/sql/SqlTimestampTypeInfo.java b/src/com/google/gwtorm/schema/sql/SqlTimestampTypeInfo.java
new file mode 100644
index 0000000..7eed577
--- /dev/null
+++ b/src/com/google/gwtorm/schema/sql/SqlTimestampTypeInfo.java
@@ -0,0 +1,44 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.schema.sql;
+
+import com.google.gwtorm.client.Column;
+import com.google.gwtorm.schema.ColumnModel;
+
+import java.sql.Types;
+
+public class SqlTimestampTypeInfo extends SqlTypeInfo {
+  @Override
+  protected String getJavaSqlTypeAlias() {
+    return "Timestamp";
+  }
+
+  @Override
+  protected int getSqlTypeConstant() {
+    return Types.TIMESTAMP;
+  }
+
+  @Override
+  public String getSqlType(final ColumnModel col) {
+    final Column column = col.getColumnAnnotation();
+    final StringBuilder r = new StringBuilder();
+    r.append("TIMESTAMP");
+    if (column.notNull()) {
+      r.append(" DEFAULT '1900-01-01 00:00:00'");
+      r.append(" NOT NULL");
+    }
+    return r.toString();
+  }
+}
diff --git a/src/com/google/gwtorm/schema/sql/SqlTypeInfo.java b/src/com/google/gwtorm/schema/sql/SqlTypeInfo.java
new file mode 100644
index 0000000..75e656d
--- /dev/null
+++ b/src/com/google/gwtorm/schema/sql/SqlTypeInfo.java
@@ -0,0 +1,60 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.schema.sql;
+
+import com.google.gwtorm.jdbc.gen.CodeGenSupport;
+import com.google.gwtorm.schema.ColumnModel;
+
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.sql.PreparedStatement;
+
+
+public abstract class SqlTypeInfo {
+  protected SqlTypeInfo() {
+  }
+
+  public abstract String getSqlType(ColumnModel column);
+
+  protected abstract String getJavaSqlTypeAlias();
+
+  protected abstract int getSqlTypeConstant();
+
+  public void generatePreparedStatementSet(final CodeGenSupport cgs) {
+    cgs.pushSqlHandle();
+    cgs.pushColumnIndex();
+    cgs.pushFieldValue();
+    cgs.invokePreparedStatementSet(getJavaSqlTypeAlias());
+  }
+
+  public void generatePreparedStatementNull(final CodeGenSupport cgs) {
+    cgs.pushSqlHandle();
+    cgs.pushColumnIndex();
+    cgs.push(getSqlTypeConstant());
+    cgs.mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type
+        .getInternalName(PreparedStatement.class), "setNull", Type
+        .getMethodDescriptor(Type.VOID_TYPE, new Type[] {Type.INT_TYPE,
+            Type.INT_TYPE}));
+  }
+
+  public void generateResultSetGet(final CodeGenSupport cgs) {
+    cgs.fieldSetBegin();
+    cgs.pushSqlHandle();
+    cgs.pushColumnIndex();
+    cgs.invokeResultSetGet(getJavaSqlTypeAlias());
+    cgs.fieldSetEnd();
+  }
+}
diff --git a/test/com/google/gwtorm/client/IntKeyTestCase.java b/test/com/google/gwtorm/client/IntKeyTestCase.java
new file mode 100644
index 0000000..8147ca4
--- /dev/null
+++ b/test/com/google/gwtorm/client/IntKeyTestCase.java
@@ -0,0 +1,112 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.client;
+
+import junit.framework.TestCase;
+
+
+public class IntKeyTestCase extends TestCase {
+  private abstract static class IntKeyImpl<T extends Key<?>> extends IntKey<T> {
+    @Column
+    int id;
+
+    public IntKeyImpl(int n) {
+      id = n;
+    }
+
+    @Override
+    public int get() {
+      return id;
+    }
+  }
+
+  private static class Parent extends IntKeyImpl<Key<?>> {
+    public Parent(int n) {
+      super(n);
+    }
+  }
+
+  private static class UnrelatedEntity extends IntKeyImpl<Key<?>> {
+    public UnrelatedEntity(int n) {
+      super(n);
+    }
+  }
+
+  private static class Child extends IntKeyImpl<Parent> {
+    private Parent parent;
+
+    public Child(Parent p, int n) {
+      super(n);
+      parent = p;
+    }
+
+    @Override
+    public Parent getParentKey() {
+      return parent;
+    }
+  }
+
+  public void testHashCodeWhenNull() {
+    final Parent p = new Parent(0);
+    assertEquals(0, p.hashCode());
+  }
+
+  public void testParentHashCode() {
+    final int id = 42;
+    final Parent p = new Parent(id);
+    assertEquals(id, p.hashCode());
+  }
+
+  public void testParentEquals() {
+    final int id = 42;
+    final Parent p1 = new Parent(id);
+    final Parent p2 = new Parent(id);
+    assertTrue(p1.equals(p1));
+    assertTrue(p1.equals(p2));
+    assertTrue(p2.equals(p1));
+    assertFalse(p1.equals(null));
+
+    final UnrelatedEntity u = new UnrelatedEntity(id);
+    assertFalse(p1.equals(u));
+    assertFalse(u.equals(p1));
+
+    final Parent p3 = new Parent(64);
+    assertFalse(p1.equals(p3));
+  }
+
+  public void testChildHashCode() {
+    final int pId = 2;
+    final int cId = 8;
+    final Parent p = new Parent(pId);
+    final Child c = new Child(p, cId);
+    assertSame(p, c.getParentKey());
+    assertTrue(cId != c.hashCode());
+  }
+
+  public void testChildEquals() {
+    final int pId = 2;
+    final int cId = 8;
+    final Child c1 = new Child(new Parent(pId), cId);
+    final Child c2 = new Child(new Parent(pId), cId);
+    assertTrue(c1.equals(c1));
+    assertTrue(c1.equals(c2));
+    assertTrue(c2.equals(c1));
+    assertFalse(c1.equals(null));
+
+    final UnrelatedEntity u = new UnrelatedEntity(cId);
+    assertFalse(c1.equals(u));
+    assertFalse(u.equals(c1));
+  }
+}
diff --git a/test/com/google/gwtorm/client/LongKeyTestCase.java b/test/com/google/gwtorm/client/LongKeyTestCase.java
new file mode 100644
index 0000000..34958cf
--- /dev/null
+++ b/test/com/google/gwtorm/client/LongKeyTestCase.java
@@ -0,0 +1,113 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.client;
+
+import junit.framework.TestCase;
+
+
+public class LongKeyTestCase extends TestCase {
+  private abstract static class LongKeyImpl<T extends Key<?>> extends
+      LongKey<T> {
+    @Column
+    long id;
+
+    public LongKeyImpl(long n) {
+      id = n;
+    }
+
+    @Override
+    public long get() {
+      return id;
+    }
+  }
+
+  private static class Parent extends LongKeyImpl<Key<?>> {
+    public Parent(long n) {
+      super(n);
+    }
+  }
+
+  private static class UnrelatedEntity extends LongKeyImpl<Key<?>> {
+    public UnrelatedEntity(long n) {
+      super(n);
+    }
+  }
+
+  private static class Child extends LongKeyImpl<Parent> {
+    private Parent parent;
+
+    public Child(Parent p, long n) {
+      super(n);
+      parent = p;
+    }
+
+    @Override
+    public Parent getParentKey() {
+      return parent;
+    }
+  }
+
+  public void testHashCodeWhenNull() {
+    final Parent p = new Parent(0);
+    assertEquals(0, p.hashCode());
+  }
+
+  public void testParentHashCode() {
+    final long id = 21281821821821881L;
+    final Parent p = new Parent(id);
+    assertEquals((int) id, p.hashCode());
+  }
+
+  public void testParentEquals() {
+    final long id = 21281821821821881L;
+    final Parent p1 = new Parent(id);
+    final Parent p2 = new Parent(id);
+    assertTrue(p1.equals(p1));
+    assertTrue(p1.equals(p2));
+    assertTrue(p2.equals(p1));
+    assertFalse(p1.equals(null));
+
+    final UnrelatedEntity u = new UnrelatedEntity(id);
+    assertFalse(p1.equals(u));
+    assertFalse(u.equals(p1));
+
+    final Parent p3 = new Parent(64);
+    assertFalse(p1.equals(p3));
+  }
+
+  public void testChildHashCode() {
+    final long pId = 21281821821821881L;
+    final long cId = 8;
+    final Parent p = new Parent(pId);
+    final Child c = new Child(p, cId);
+    assertSame(p, c.getParentKey());
+    assertTrue(cId != c.hashCode());
+  }
+
+  public void testChildEquals() {
+    final long pId = 21281821821821881L;
+    final long cId = 8;
+    final Child c1 = new Child(new Parent(pId), cId);
+    final Child c2 = new Child(new Parent(pId), cId);
+    assertTrue(c1.equals(c1));
+    assertTrue(c1.equals(c2));
+    assertTrue(c2.equals(c1));
+    assertFalse(c1.equals(null));
+
+    final UnrelatedEntity u = new UnrelatedEntity(cId);
+    assertFalse(c1.equals(u));
+    assertFalse(u.equals(c1));
+  }
+}
diff --git a/test/com/google/gwtorm/client/StringKeyTestCase.java b/test/com/google/gwtorm/client/StringKeyTestCase.java
new file mode 100644
index 0000000..62dfbd0
--- /dev/null
+++ b/test/com/google/gwtorm/client/StringKeyTestCase.java
@@ -0,0 +1,113 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.client;
+
+import junit.framework.TestCase;
+
+
+public class StringKeyTestCase extends TestCase {
+  private abstract static class StringKeyImpl<T extends Key<?>> extends
+      StringKey<T> {
+    @Column
+    String name;
+
+    public StringKeyImpl(String n) {
+      name = n;
+    }
+
+    @Override
+    public String get() {
+      return name;
+    }
+  }
+
+  private static class Parent extends StringKeyImpl<Key<?>> {
+    public Parent(String n) {
+      super(n);
+    }
+  }
+
+  private static class UnrelatedEntity extends StringKeyImpl<Key<?>> {
+    public UnrelatedEntity(String n) {
+      super(n);
+    }
+  }
+
+  private static class Child extends StringKeyImpl<Parent> {
+    private Parent parent;
+
+    public Child(Parent p, String n) {
+      super(n);
+      parent = p;
+    }
+
+    @Override
+    public Parent getParentKey() {
+      return parent;
+    }
+  }
+
+  public void testHashCodeWhenNull() {
+    final Parent p = new Parent(null);
+    assertEquals(0, p.hashCode());
+  }
+
+  public void testParentHashCode() {
+    final String str = "foo";
+    final Parent p = new Parent(str);
+    assertEquals(str.hashCode(), p.hashCode());
+  }
+
+  public void testParentEquals() {
+    final String str = "foo";
+    final Parent p1 = new Parent(str);
+    final Parent p2 = new Parent(str);
+    assertTrue(p1.equals(p1));
+    assertTrue(p1.equals(p2));
+    assertTrue(p2.equals(p1));
+    assertFalse(p1.equals(null));
+
+    final UnrelatedEntity u = new UnrelatedEntity(str);
+    assertFalse(p1.equals(u));
+    assertFalse(u.equals(p1));
+
+    final Parent p3 = new Parent("bar");
+    assertFalse(p1.equals(p3));
+  }
+
+  public void testChildHashCode() {
+    final String pName = "foo";
+    final String cName = "bar";
+    final Parent p = new Parent(pName);
+    final Child c = new Child(p, cName);
+    assertSame(p, c.getParentKey());
+    assertTrue(cName.hashCode() != c.hashCode());
+  }
+
+  public void testChildEquals() {
+    final String pName = "foo";
+    final String cName = "bar";
+    final Child c1 = new Child(new Parent(pName), cName);
+    final Child c2 = new Child(new Parent(pName), cName);
+    assertTrue(c1.equals(c1));
+    assertTrue(c1.equals(c2));
+    assertTrue(c2.equals(c1));
+    assertFalse(c1.equals(null));
+
+    final UnrelatedEntity u = new UnrelatedEntity("bar");
+    assertFalse(c1.equals(u));
+    assertFalse(u.equals(c1));
+  }
+}
diff --git a/test/com/google/gwtorm/data/AddressAccess.java b/test/com/google/gwtorm/data/AddressAccess.java
new file mode 100644
index 0000000..ef98582
--- /dev/null
+++ b/test/com/google/gwtorm/data/AddressAccess.java
@@ -0,0 +1,29 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.data;
+
+import com.google.gwtorm.client.Access;
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.PrimaryKey;
+import com.google.gwtorm.client.Query;
+import com.google.gwtorm.client.ResultSet;
+
+public interface AddressAccess extends Access<TestAddress, TestAddress.Key> {
+  @PrimaryKey("city")
+  TestAddress byId(TestAddress.Key a) throws OrmException;
+
+  @Query()
+  ResultSet<TestAddress> all() throws OrmException;
+}
diff --git a/test/com/google/gwtorm/data/PersonAccess.java b/test/com/google/gwtorm/data/PersonAccess.java
new file mode 100644
index 0000000..7df107e
--- /dev/null
+++ b/test/com/google/gwtorm/data/PersonAccess.java
@@ -0,0 +1,48 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.data;
+
+import com.google.gwtorm.client.Access;
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.PrimaryKey;
+import com.google.gwtorm.client.Query;
+import com.google.gwtorm.client.ResultSet;
+
+public interface PersonAccess extends Access<TestPerson, TestPerson.Key> {
+  @PrimaryKey("name")
+  TestPerson get(TestPerson.Key key) throws OrmException;
+
+  @Query
+  ResultSet<TestPerson> all() throws OrmException;
+
+  @Query("WHERE age > ? ORDER BY age")
+  ResultSet<TestPerson> olderThan(int age) throws OrmException;
+
+  @Query("WHERE name != ? AND age > ? ORDER BY name DESC")
+  ResultSet<TestPerson> notPerson(TestPerson.Key key, int age)
+      throws OrmException;
+
+  @Query("WHERE name = 'bob' LIMIT ?")
+  ResultSet<TestPerson> firstNBob(int n) throws OrmException;
+
+  @Query("WHERE registered = false ORDER BY name")
+  ResultSet<TestPerson> notRegistered() throws OrmException;
+
+  @Query("ORDER BY age LIMIT 1")
+  ResultSet<TestPerson> youngest() throws OrmException;
+
+  @Query("ORDER BY age LIMIT ?")
+  ResultSet<TestPerson> youngestN(int n) throws OrmException;
+}
diff --git a/test/com/google/gwtorm/data/PhoneBookDb.java b/test/com/google/gwtorm/data/PhoneBookDb.java
new file mode 100644
index 0000000..15439b8
--- /dev/null
+++ b/test/com/google/gwtorm/data/PhoneBookDb.java
@@ -0,0 +1,30 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.data;
+
+import com.google.gwtorm.client.Relation;
+import com.google.gwtorm.client.Schema;
+import com.google.gwtorm.client.Sequence;
+
+public interface PhoneBookDb extends Schema {
+  @Relation
+  PersonAccess people();
+
+  @Relation
+  AddressAccess addresses();
+
+  @Sequence
+  int nextAddressId();
+}
diff --git a/test/com/google/gwtorm/data/TestAddress.java b/test/com/google/gwtorm/data/TestAddress.java
new file mode 100644
index 0000000..d6a135a
--- /dev/null
+++ b/test/com/google/gwtorm/data/TestAddress.java
@@ -0,0 +1,61 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.data;
+
+import com.google.gwtorm.client.Column;
+import com.google.gwtorm.client.StringKey;
+
+public class TestAddress {
+  public static class Key extends StringKey<TestPerson.Key> {
+    @Column
+    protected TestPerson.Key owner;
+
+    @Column
+    protected String name;
+
+    protected Key() {
+      owner = new TestPerson.Key();
+    }
+
+    public Key(final TestPerson.Key owner, final String name) {
+      this.owner = owner;
+      this.name = name;
+    }
+
+    @Override
+    public String get() {
+      return name;
+    }
+
+    @Override
+    public TestPerson.Key getParentKey() {
+      return owner;
+    }
+  }
+
+  @Column
+  protected Key city;
+
+  protected TestAddress() {
+  }
+
+  public TestAddress(final TestAddress.Key city) {
+    this.city = city;
+  }
+
+  public String city() {
+    return city.name;
+  }
+}
diff --git a/test/com/google/gwtorm/data/TestPerson.java b/test/com/google/gwtorm/data/TestPerson.java
new file mode 100644
index 0000000..7cce083
--- /dev/null
+++ b/test/com/google/gwtorm/data/TestPerson.java
@@ -0,0 +1,79 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.data;
+
+import com.google.gwtorm.client.Column;
+import com.google.gwtorm.client.StringKey;
+
+
+public class TestPerson {
+  public static class Key extends StringKey {
+    @Column(length = 20)
+    protected String name;
+
+    protected Key() {
+    }
+
+    public Key(final String name) {
+      this.name = name;
+    }
+
+    @Override
+    public String get() {
+      return name;
+    }
+  }
+
+  @Column
+  protected Key name;
+
+  @Column
+  protected int age;
+
+  @Column
+  protected boolean registered;
+
+  protected TestPerson() {
+  }
+
+  public TestPerson(final Key key, final int age) {
+    this.name = key;
+    this.age = age;
+  }
+
+  public String name() {
+    return name.get();
+  }
+
+  public int age() {
+    return age;
+  }
+
+  public boolean isRegistered() {
+    return registered;
+  }
+
+  public void growOlder() {
+    age++;
+  }
+
+  public void register() {
+    registered = true;
+  }
+
+  public void unregister() {
+    registered = false;
+  }
+}
diff --git a/test/com/google/gwtorm/schema/QueryParserTest.java b/test/com/google/gwtorm/schema/QueryParserTest.java
new file mode 100644
index 0000000..3f5bd90
--- /dev/null
+++ b/test/com/google/gwtorm/schema/QueryParserTest.java
@@ -0,0 +1,178 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.schema;
+
+import com.google.gwtorm.client.OrmException;
+
+import junit.framework.TestCase;
+
+import org.antlr.runtime.tree.Tree;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+public class QueryParserTest extends TestCase {
+  private final class DummyColumn extends ColumnModel {
+    private String name;
+
+    DummyColumn(final String n) {
+      name = n;
+      columnName = n;
+    }
+
+    @Override
+    public String getFieldName() {
+      return name;
+    }
+
+    @Override
+    public String getNestedClassName() {
+      return null;
+    }
+
+    @Override
+    public Class<?> getPrimitiveType() {
+      return String.class;
+    }
+  }
+
+  protected Tree parse(final String str) throws QueryParseException {
+    final RelationModel dummy = new RelationModel() {
+      {
+        final Collection<ColumnModel> c = new ArrayList<ColumnModel>();
+        try {
+          c.add(new DummyColumn("name"));
+          c.add(new DummyColumn("a"));
+          c.add(new DummyColumn("b"));
+          c.add(new DummyColumn("c"));
+          initColumns(c);
+        } catch (OrmException e) {
+          throw new RuntimeException("init columns failure", e);
+        }
+      }
+
+      @Override
+      public String getAccessInterfaceName() {
+        return getClass().getName();
+      }
+
+      @Override
+      public String getEntityTypeClassName() {
+        return getClass().getName();
+      }
+    };
+    return QueryParser.parse(dummy, str);
+  }
+
+  protected static void assertGoodEQ(final Tree c, final String fieldName) {
+    assertEquals(QueryParser.EQ, c.getType());
+    assertEquals(2, c.getChildCount());
+    assertEquals(QueryParser.ID, c.getChild(0).getType());
+    assertTrue(c.getChild(0) instanceof QueryParser.Column);
+    assertEquals(fieldName, c.getChild(0).getText());
+    assertEquals(QueryParser.PLACEHOLDER, c.getChild(1).getType());
+  }
+
+  public void testEmptyQuery() throws QueryParseException {
+    assertNull(parse(""));
+  }
+
+  public void testWhereNameEq() throws QueryParseException {
+    final Tree t = parse("WHERE name = ?");
+    assertNotNull(t);
+    assertEquals(QueryParser.WHERE, t.getType());
+
+    assertEquals(1, t.getChildCount());
+    assertGoodEQ(t.getChild(0), "name");
+  }
+
+  public void testWhereAAndBAndC() throws QueryParseException {
+    final Tree t = parse("WHERE a = ? AND b = ? AND c = ?");
+    assertNotNull(t);
+    assertEquals(QueryParser.WHERE, t.getType());
+
+    assertEquals(1, t.getChildCount());
+    final Tree c = t.getChild(0);
+    assertEquals(QueryParser.AND, c.getType());
+    assertEquals(3, c.getChildCount());
+    assertGoodEQ(c.getChild(0), "a");
+    assertGoodEQ(c.getChild(1), "b");
+    assertGoodEQ(c.getChild(2), "c");
+  }
+
+  public void testOrderByA() throws QueryParseException {
+    final Tree t = parse("ORDER BY a");
+    assertNotNull(t);
+    assertEquals(QueryParser.ORDER, t.getType());
+    assertEquals(1, t.getChildCount());
+
+    final Tree a = t.getChild(0);
+    assertEquals(QueryParser.ASC, a.getType());
+    assertEquals(1, a.getChildCount());
+    assertEquals(QueryParser.ID, a.getChild(0).getType());
+    assertTrue(a.getChild(0) instanceof QueryParser.Column);
+    assertEquals("a", a.getChild(0).getText());
+  }
+
+  public void testOrderByAB() throws QueryParseException {
+    final Tree t = parse("ORDER BY a DESC, b ASC");
+    assertNotNull(t);
+    assertEquals(QueryParser.ORDER, t.getType());
+    assertEquals(2, t.getChildCount());
+    {
+      final Tree a = t.getChild(0);
+      assertEquals(QueryParser.DESC, a.getType());
+      assertEquals(1, a.getChildCount());
+      assertEquals(QueryParser.ID, a.getChild(0).getType());
+      assertTrue(a.getChild(0) instanceof QueryParser.Column);
+      assertEquals("a", a.getChild(0).getText());
+    }
+    {
+      final Tree b = t.getChild(1);
+      assertEquals(QueryParser.ASC, b.getType());
+      assertEquals(1, b.getChildCount());
+      assertEquals(QueryParser.ID, b.getChild(0).getType());
+      assertTrue(b.getChild(0) instanceof QueryParser.Column);
+      assertEquals("b", b.getChild(0).getText());
+    }
+  }
+
+  public void testWhereAOrderByA() throws QueryParseException {
+    final Tree t = parse("WHERE a = ? ORDER BY a");
+    assertNotNull(t);
+    assertEquals(0, t.getType());
+    assertEquals(2, t.getChildCount());
+    {
+      final Tree w = t.getChild(0);
+      assertEquals(QueryParser.WHERE, w.getType());
+      assertEquals(1, w.getChildCount());
+      assertGoodEQ(w.getChild(0), "a");
+    }
+    {
+      final Tree o = t.getChild(1);
+      assertEquals(QueryParser.ORDER, o.getType());
+      assertEquals(1, o.getChildCount());
+
+      final Tree a = o.getChild(0);
+      assertEquals(QueryParser.ASC, a.getType());
+      assertEquals(1, a.getChildCount());
+      final Tree aId = a.getChild(0);
+      assertEquals(QueryParser.ID, aId.getType());
+      assertTrue(aId instanceof QueryParser.Column);
+      assertEquals("a", aId.getText());
+      assertEquals("a", ((QueryParser.Column) aId).getField().getFieldName());
+    }
+  }
+}
diff --git a/test/com/google/gwtorm/server/PhoneBookDbTestCase.java b/test/com/google/gwtorm/server/PhoneBookDbTestCase.java
new file mode 100644
index 0000000..7252d85
--- /dev/null
+++ b/test/com/google/gwtorm/server/PhoneBookDbTestCase.java
@@ -0,0 +1,353 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.server;
+
+import com.google.gwtorm.client.OrmException;
+import com.google.gwtorm.client.Transaction;
+import com.google.gwtorm.data.PersonAccess;
+import com.google.gwtorm.data.PhoneBookDb;
+import com.google.gwtorm.data.TestPerson;
+import com.google.gwtorm.jdbc.Database;
+import com.google.gwtorm.jdbc.JdbcSchema;
+
+import junit.framework.TestCase;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Properties;
+
+public class PhoneBookDbTestCase extends TestCase {
+  private static int runCount;
+  protected Database<PhoneBookDb> db;
+  private List<PhoneBookDb> openSchemas;
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+
+    final Properties p = new Properties();
+    p.setProperty("driver", org.h2.Driver.class.getName());
+    p.setProperty("url", "jdbc:h2:mem:PhoneBookDb" + (runCount++));
+    db = new Database<PhoneBookDb>(p, PhoneBookDb.class);
+    openSchemas = new ArrayList<PhoneBookDb>();
+  }
+
+  @Override
+  public void tearDown() throws Exception {
+    if (openSchemas != null) {
+      for (PhoneBookDb schema : openSchemas) {
+        schema.close();
+      }
+      openSchemas = null;
+    }
+    super.tearDown();
+  }
+
+  protected PhoneBookDb open() throws OrmException {
+    final PhoneBookDb r = db.open();
+    if (r != null) {
+      openSchemas.add(r);
+    }
+    return r;
+  }
+
+  protected PhoneBookDb openAndCreate() throws OrmException {
+    final PhoneBookDb schema = open();
+    schema.createSchema();
+    return schema;
+  }
+
+  protected Statement statement(final PhoneBookDb schema) throws SQLException {
+    return ((JdbcSchema) schema).getConnection().createStatement();
+  }
+
+  public void testCreateDatabaseHandle() throws Exception {
+    assertNotNull(db);
+  }
+
+  public void testOpenSchema() throws Exception {
+    final PhoneBookDb schema1 = open();
+    assertNotNull(schema1);
+
+    final PhoneBookDb schema2 = open();
+    assertNotNull(schema2);
+    assertNotSame(schema1, schema2);
+  }
+
+  public void testGetPeopleAccess() throws Exception {
+    final PhoneBookDb schema = open();
+    assertNotNull(schema.people());
+  }
+
+  public void testGetAddressAccess() throws Exception {
+    final PhoneBookDb schema = open();
+    assertNotNull(schema.addresses());
+  }
+
+  public void testCreateSchema() throws Exception {
+    final PhoneBookDb schema = open();
+    schema.createSchema();
+  }
+
+  public void testNextAddressId() throws Exception {
+    final PhoneBookDb schema = openAndCreate();
+    final int a = schema.nextAddressId();
+    final int b = schema.nextAddressId();
+    assertTrue(a != b);
+  }
+
+  public void testPersonPrimaryKey() throws Exception {
+    final PhoneBookDb schema = openAndCreate();
+    final TestPerson.Key key = new TestPerson.Key("Bob");
+    final TestPerson bob = new TestPerson(key, 18);
+    assertSame(key, schema.people().primaryKey(bob));
+  }
+
+  public void testInsertOnePerson() throws Exception {
+    final PhoneBookDb schema = openAndCreate();
+    final TestPerson bob = new TestPerson(new TestPerson.Key("Bob"), 18);
+    schema.people().insert(Collections.singleton(bob));
+
+    final Statement st = statement(schema);
+    final ResultSet rs = st.executeQuery("SELECT name,age FROM people");
+    assertTrue(rs.next());
+    assertEquals(bob.name(), rs.getString(1));
+    assertEquals(bob.age(), rs.getInt(2));
+    assertFalse(rs.next());
+    rs.close();
+    st.close();
+  }
+
+  public void testGetOnePerson() throws Exception {
+    final PhoneBookDb schema = openAndCreate();
+    final PersonAccess sp = schema.people();
+    final TestPerson p1 = new TestPerson(new TestPerson.Key("Bob"), 18);
+    sp.insert(Collections.singleton(p1));
+
+    final TestPerson p2 = sp.get(sp.primaryKey(p1));
+    assertNotNull(p2);
+    assertNotSame(p1, p2);
+    assertEquals(sp.primaryKey(p1), sp.primaryKey(p2));
+  }
+
+  public void testGetOnePersonIterator() throws Exception {
+    final PhoneBookDb schema = openAndCreate();
+    final PersonAccess sp = schema.people();
+    final TestPerson p1 = new TestPerson(new TestPerson.Key("Bob"), 18);
+    sp.insert(Collections.singleton(p1));
+
+    final List<TestPerson> list =
+        sp.get(Collections.singleton(sp.primaryKey(p1))).toList();
+    assertNotNull(list);
+    assertEquals(1, list.size());
+
+    final TestPerson p2 = list.get(0);
+    assertNotNull(p2);
+    assertNotSame(p1, p2);
+    assertEquals(sp.primaryKey(p1), sp.primaryKey(p2));
+  }
+
+  public void testInsertManyPeople() throws Exception {
+    final PhoneBookDb schema = openAndCreate();
+    final ArrayList<TestPerson> all = new ArrayList<TestPerson>();
+    all.add(new TestPerson(new TestPerson.Key("Bob"), 18));
+    all.add(new TestPerson(new TestPerson.Key("Mary"), 22));
+    all.add(new TestPerson(new TestPerson.Key("Zak"), 33));
+    schema.people().insert(all);
+
+    final Statement st = statement(schema);
+    final ResultSet rs;
+    rs = st.executeQuery("SELECT name,age FROM people ORDER BY name");
+    for (int rowIdx = 0; rowIdx < all.size(); rowIdx++) {
+      assertTrue(rs.next());
+      assertEquals(all.get(rowIdx).name(), rs.getString(1));
+      assertEquals(all.get(rowIdx).age(), rs.getInt(2));
+    }
+    assertFalse(rs.next());
+    rs.close();
+    st.close();
+  }
+
+  public void testInsertManyPeopleByTransaction() throws Exception {
+    final PhoneBookDb schema = openAndCreate();
+    final Transaction txn = schema.beginTransaction();
+    final ArrayList<TestPerson> all = new ArrayList<TestPerson>();
+    all.add(new TestPerson(new TestPerson.Key("Bob"), 18));
+    all.add(new TestPerson(new TestPerson.Key("Mary"), 22));
+    all.add(new TestPerson(new TestPerson.Key("Zak"), 33));
+    schema.people().insert(all, txn);
+
+    final Statement st = statement(schema);
+    ResultSet rs;
+
+    rs = st.executeQuery("SELECT name,age FROM people ORDER BY name");
+    assertFalse(rs.next());
+    rs.close();
+
+    txn.commit();
+    rs = st.executeQuery("SELECT name,age FROM people ORDER BY name");
+    for (int rowIdx = 0; rowIdx < all.size(); rowIdx++) {
+      assertTrue(rs.next());
+      assertEquals(all.get(rowIdx).name(), rs.getString(1));
+      assertEquals(all.get(rowIdx).age(), rs.getInt(2));
+    }
+    assertFalse(rs.next());
+    rs.close();
+    st.close();
+  }
+
+  public void testDeleteOnePerson() throws Exception {
+    final PhoneBookDb schema = openAndCreate();
+    final TestPerson bob = new TestPerson(new TestPerson.Key("Bob"), 18);
+    schema.people().insert(Collections.singleton(bob));
+    schema.people().delete(Collections.singleton(bob));
+
+    final Statement st = statement(schema);
+    final ResultSet rs = st.executeQuery("SELECT name,age FROM people");
+    assertFalse(rs.next());
+    rs.close();
+    st.close();
+  }
+
+  public void testUpdateOnePerson() throws Exception {
+    final PhoneBookDb schema = openAndCreate();
+    final TestPerson bob = new TestPerson(new TestPerson.Key("Bob"), 18);
+    schema.people().insert(Collections.singleton(bob));
+    bob.growOlder();
+    schema.people().update(Collections.singleton(bob));
+
+    final Statement st = statement(schema);
+    final ResultSet rs = st.executeQuery("SELECT name,age FROM people");
+    assertTrue(rs.next());
+    assertEquals(bob.name(), rs.getString(1));
+    assertEquals(bob.age(), rs.getInt(2));
+    assertFalse(rs.next());
+    rs.close();
+    st.close();
+  }
+
+  public void testUpdateNoPerson() throws Exception {
+    final PhoneBookDb schema = openAndCreate();
+    final TestPerson bob = new TestPerson(new TestPerson.Key("Bob"), 18);
+    try {
+      schema.people().update(Collections.singleton(bob));
+      fail("Update of missing person succeeded");
+    } catch (OrmException e) {
+      assertEquals("Update failure: people", e.getMessage());
+      assertTrue(e.getCause() instanceof SQLException);
+      assertEquals("Entity 1 not affected by update", e.getCause().getMessage());
+    }
+  }
+
+  public void testFetchOnePerson() throws Exception {
+    final PhoneBookDb schema = openAndCreate();
+    final TestPerson bob = new TestPerson(new TestPerson.Key("Bob"), 18);
+    schema.people().insert(Collections.singleton(bob));
+
+    final List<TestPerson> all = schema.people().all().toList();
+    assertNotNull(all);
+    assertEquals(1, all.size());
+    assertNotSame(bob, all.get(0));
+    assertEquals(bob.name(), all.get(0).name());
+    assertEquals(bob.age(), all.get(0).age());
+    assertEquals(bob.isRegistered(), all.get(0).isRegistered());
+  }
+
+  public void testFetchOnePersonByName() throws Exception {
+    final PhoneBookDb schema = openAndCreate();
+    final TestPerson bob1 = new TestPerson(new TestPerson.Key("Bob"), 18);
+    schema.people().insert(Collections.singleton(bob1));
+
+    final TestPerson bob2 =
+        schema.people().get(new TestPerson.Key(bob1.name()));
+    assertNotNull(bob2);
+    assertNotSame(bob1, bob2);
+    assertEquals(bob1.name(), bob2.name());
+    assertEquals(bob1.age(), bob2.age());
+    assertEquals(bob1.isRegistered(), bob2.isRegistered());
+  }
+
+  public void testFetchByAge() throws Exception {
+    final PhoneBookDb schema = openAndCreate();
+    final ArrayList<TestPerson> all = new ArrayList<TestPerson>();
+    all.add(new TestPerson(new TestPerson.Key("Bob"), 18));
+    all.add(new TestPerson(new TestPerson.Key("Mary"), 22));
+    all.add(new TestPerson(new TestPerson.Key("Zak"), 33));
+    schema.people().insert(all);
+
+    final List<TestPerson> r = schema.people().olderThan(20).toList();
+    assertEquals(2, r.size());
+    assertEquals(all.get(1).name(), r.get(0).name());
+    assertEquals(all.get(2).name(), r.get(1).name());
+  }
+
+  public void testFetchNotPerson() throws Exception {
+    final PhoneBookDb schema = openAndCreate();
+    final ArrayList<TestPerson> all = new ArrayList<TestPerson>();
+    all.add(new TestPerson(new TestPerson.Key("Bob"), 18));
+    all.add(new TestPerson(new TestPerson.Key("Mary"), 22));
+    all.add(new TestPerson(new TestPerson.Key("Zak"), 33));
+    schema.people().insert(all);
+
+    final List<TestPerson> r =
+        schema.people().notPerson(new TestPerson.Key("Mary"), 10).toList();
+    assertEquals(2, r.size());
+    assertEquals(all.get(2).name(), r.get(0).name());
+    assertEquals(all.get(0).name(), r.get(1).name());
+  }
+
+  public void testBooleanType() throws Exception {
+    final PhoneBookDb schema = openAndCreate();
+    final TestPerson bob = new TestPerson(new TestPerson.Key("Bob"), 18);
+    schema.people().insert(Collections.singleton(bob));
+
+    final Statement st = statement(schema);
+    ResultSet rs;
+
+    rs = st.executeQuery("SELECT registered FROM people");
+    assertTrue(rs.next());
+    assertEquals("N", rs.getString(1));
+    assertFalse(rs.next());
+    rs.close();
+    assertEquals(bob.isRegistered(), schema.people().all().toList().get(0)
+        .isRegistered());
+
+    bob.register();
+    schema.people().update(Collections.singleton(bob));
+    rs = st.executeQuery("SELECT registered FROM people");
+    assertTrue(rs.next());
+    assertEquals("Y", rs.getString(1));
+    assertFalse(rs.next());
+    rs.close();
+    assertEquals(bob.isRegistered(), schema.people().all().toList().get(0)
+        .isRegistered());
+
+    bob.unregister();
+    schema.people().update(Collections.singleton(bob));
+    rs = st.executeQuery("SELECT registered FROM people");
+    assertTrue(rs.next());
+    assertEquals("N", rs.getString(1));
+    assertFalse(rs.next());
+    rs.close();
+    assertEquals(bob.isRegistered(), schema.people().all().toList().get(0)
+        .isRegistered());
+
+    st.close();
+  }
+}
diff --git a/test/com/google/gwtorm/server/PrintCreateTablesTestCase.java b/test/com/google/gwtorm/server/PrintCreateTablesTestCase.java
new file mode 100644
index 0000000..544c185
--- /dev/null
+++ b/test/com/google/gwtorm/server/PrintCreateTablesTestCase.java
@@ -0,0 +1,28 @@
+// Copyright 2008 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gwtorm.server;
+
+import com.google.gwtorm.data.PhoneBookDb;
+import com.google.gwtorm.schema.java.JavaSchemaModel;
+import com.google.gwtorm.schema.sql.DialectH2;
+
+import junit.framework.TestCase;
+
+public class PrintCreateTablesTestCase extends TestCase {
+  public void testCreate() throws Exception {
+    final JavaSchemaModel m = new JavaSchemaModel(PhoneBookDb.class);
+    System.out.println(m.getCreateDatabaseSql(new DialectH2()));
+  }
+}