You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@polygene.apache.org by ni...@apache.org on 2017/09/18 15:21:03 UTC

[07/15] polygene-java git commit: entitystore-sql is brought in from the es-jooq branch, and fixed it up so that it compiles and passes the standard unit tests using H2. More testing needed for other SQL systems, since there seems to not be consensus on

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/6055b8f7/extensions/entitystore-jooq/src/main/java/org/apache/polygene/entitystore/jooq/SqlType.java
----------------------------------------------------------------------
diff --git a/extensions/entitystore-jooq/src/main/java/org/apache/polygene/entitystore/jooq/SqlType.java b/extensions/entitystore-jooq/src/main/java/org/apache/polygene/entitystore/jooq/SqlType.java
deleted file mode 100644
index 6b6dfdd..0000000
--- a/extensions/entitystore-jooq/src/main/java/org/apache/polygene/entitystore/jooq/SqlType.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 org.apache.polygene.entitystore.jooq;
-
-import java.time.Instant;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.time.LocalTime;
-import java.time.OffsetDateTime;
-import java.time.Period;
-import java.time.ZonedDateTime;
-import org.jooq.DataType;
-import org.jooq.impl.SQLDataType;
-import org.jooq.types.Interval;
-
-class SqlType
-{
-    @SuppressWarnings( "unchecked" )
-    static <T> DataType<T> getSqlDataTypeFor( Class<?> propertyType )
-    {
-        if( String.class.isAssignableFrom( propertyType ) )
-        {
-            return (DataType<T>) SQLDataType.VARCHAR;
-        }
-        if( Integer.class.isAssignableFrom( propertyType ) )
-        {
-            return (DataType<T>) SQLDataType.INTEGER;
-        }
-        if( Long.class.isAssignableFrom( propertyType ) )
-        {
-            return (DataType<T>) SQLDataType.BIGINT;
-        }
-        if( Boolean.class.isAssignableFrom( propertyType ) )
-        {
-            return (DataType<T>) SQLDataType.BOOLEAN;
-        }
-        if( Float.class.isAssignableFrom( propertyType ) )
-        {
-            return (DataType<T>) SQLDataType.FLOAT;
-        }
-        if( Double.class.isAssignableFrom( propertyType ) )
-        {
-            return (DataType<T>) SQLDataType.DOUBLE;
-        }
-        if( Instant.class.isAssignableFrom( propertyType ) )
-        {
-            return (DataType<T>) SQLDataType.TIMESTAMPWITHTIMEZONE;
-        }
-        if( Interval.class.isAssignableFrom( propertyType ) )
-        {
-            return (DataType<T>) SQLDataType.VARCHAR;
-        }
-        if( Period.class.isAssignableFrom( propertyType ) )
-        {
-            return (DataType<T>) SQLDataType.VARCHAR;
-        }
-        if( LocalDate.class.isAssignableFrom( propertyType ) )
-        {
-            return (DataType<T>) SQLDataType.LOCALDATE;
-        }
-        if( LocalTime.class.isAssignableFrom( propertyType ) )
-        {
-            return (DataType<T>) SQLDataType.LOCALTIME;
-        }
-        if( LocalDateTime.class.isAssignableFrom( propertyType ) )
-        {
-            return (DataType<T>) SQLDataType.LOCALDATETIME;
-        }
-        if( ZonedDateTime.class.isAssignableFrom( propertyType ) )
-        {
-            return (DataType<T>) SQLDataType.OFFSETDATETIME;
-        }
-        if( OffsetDateTime.class.isAssignableFrom( propertyType ) )
-        {
-            return (DataType<T>) SQLDataType.OFFSETDATETIME;
-        }
-        if( Character.class.isAssignableFrom( propertyType ) )
-        {
-            return (DataType<T>) SQLDataType.CHAR( 1 );
-        }
-        if( Short.class.isAssignableFrom( propertyType ) )
-        {
-            return (DataType<T>) SQLDataType.INTEGER;
-        }
-        if( Byte.class.isAssignableFrom( propertyType ) )
-        {
-            return (DataType<T>) SQLDataType.INTEGER;
-        }
-        if( propertyType.isPrimitive() )
-        {
-            if( propertyType.equals( Integer.TYPE ) )
-            {
-                return (DataType<T>) SQLDataType.INTEGER;
-            }
-            if( propertyType.equals( Long.TYPE ) )
-            {
-                return (DataType<T>) SQLDataType.BIGINT;
-            }
-            if( propertyType.equals( Boolean.TYPE ) )
-            {
-                return (DataType<T>) SQLDataType.BOOLEAN;
-            }
-            if( propertyType.equals( Float.TYPE ) )
-            {
-                return (DataType<T>) SQLDataType.FLOAT;
-            }
-            if( propertyType.equals( Double.TYPE ) )
-            {
-                return (DataType<T>) SQLDataType.DOUBLE;
-            }
-            if( propertyType.equals( Character.TYPE ) )
-            {
-                return (DataType<T>) SQLDataType.CHAR( 1 );
-            }
-            if( propertyType.equals( Short.TYPE ) )
-            {
-                return (DataType<T>) SQLDataType.INTEGER;
-            }
-            if( propertyType.equals( Byte.TYPE ) )
-            {
-                return (DataType<T>) SQLDataType.INTEGER;
-            }
-        }
-        return (DataType<T>) SQLDataType.VARCHAR;
-    }
-}

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/6055b8f7/extensions/entitystore-jooq/src/main/java/org/apache/polygene/entitystore/jooq/TableFields.java
----------------------------------------------------------------------
diff --git a/extensions/entitystore-jooq/src/main/java/org/apache/polygene/entitystore/jooq/TableFields.java b/extensions/entitystore-jooq/src/main/java/org/apache/polygene/entitystore/jooq/TableFields.java
deleted file mode 100644
index db42413..0000000
--- a/extensions/entitystore-jooq/src/main/java/org/apache/polygene/entitystore/jooq/TableFields.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 org.apache.polygene.entitystore.jooq;
-
-import java.sql.Timestamp;
-import org.jooq.Field;
-
-import static org.apache.polygene.entitystore.jooq.TypesTable.makeField;
-
-public interface TableFields
-{
-    // Common in all tables
-    String IDENTITY_COLUMN_NAME = "_identity";
-    String CREATED_COLUMN_NAME = "_created_at";
-    String LASTMODIFIED_COLUMN_NAME = "_modified_at";
-
-    // Types Table
-    String TABLENAME_COLUMN_NAME = "_table_name";
-
-    // Entities Table
-    String VALUEID_COLUMN_NAME = "_value_id";
-    String TYPE_COLUMN_NAME = "_type";
-    String VERSION_COLUMN_NAME = "_version";
-    String APPLICATIONVERSION_COLUMN_NAME = "_app_version";
-
-    // Mixin Tables
-    String NAME_COLUMN_NAME = "_name";
-    String INDEX_COLUMN_NAME = "_index";    // either index in ManyAssociation or name in NamedAssociation
-    String REFERENCE_COLUMN_NAME = "_reference";
-    String ASSOCS_TABLE_POSTFIX = "_ASSOCS";
-
-
-    // Common Fields
-    Field<String> identityColumn = makeField( IDENTITY_COLUMN_NAME, String.class );
-    Field<Timestamp> createdColumn = makeField( CREATED_COLUMN_NAME, Timestamp.class );
-    Field<Timestamp> modifiedColumn = makeField( LASTMODIFIED_COLUMN_NAME, Timestamp.class );
-
-    // Types Table
-    Field<String> tableNameColumn = makeField( TABLENAME_COLUMN_NAME, String.class );
-
-    // Entities Table
-    Field<String> valueIdentityColumn = makeField( VALUEID_COLUMN_NAME, String.class );
-    Field<String> typeNameColumn = makeField( TYPE_COLUMN_NAME, String.class );
-    Field<String> versionColumn = makeField( VERSION_COLUMN_NAME, String.class );
-    Field<String> applicationVersionColumn = makeField( APPLICATIONVERSION_COLUMN_NAME, String.class );
-
-    // Mixin Tables
-
-    // The _ASSOCS table
-    Field<String> nameColumn = makeField( NAME_COLUMN_NAME, String.class );
-    Field<String> referenceColumn = makeField( REFERENCE_COLUMN_NAME, String.class );
-    Field<String> indexColumn = makeField( INDEX_COLUMN_NAME, String.class );
-
-}

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/6055b8f7/extensions/entitystore-jooq/src/main/java/org/apache/polygene/entitystore/jooq/TypesTable.java
----------------------------------------------------------------------
diff --git a/extensions/entitystore-jooq/src/main/java/org/apache/polygene/entitystore/jooq/TypesTable.java b/extensions/entitystore-jooq/src/main/java/org/apache/polygene/entitystore/jooq/TypesTable.java
deleted file mode 100644
index c816c95..0000000
--- a/extensions/entitystore-jooq/src/main/java/org/apache/polygene/entitystore/jooq/TypesTable.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 org.apache.polygene.entitystore.jooq;
-
-import java.lang.reflect.Method;
-import java.lang.reflect.Type;
-import java.sql.Timestamp;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import org.apache.polygene.api.association.AssociationDescriptor;
-import org.apache.polygene.api.common.QualifiedName;
-import org.apache.polygene.api.entity.EntityDescriptor;
-import org.apache.polygene.api.property.Property;
-import org.apache.polygene.api.property.PropertyDescriptor;
-import org.apache.polygene.api.util.Classes;
-import org.jooq.CreateTableColumnStep;
-import org.jooq.DataType;
-import org.jooq.Field;
-import org.jooq.Record;
-import org.jooq.Result;
-import org.jooq.SQLDialect;
-import org.jooq.Schema;
-import org.jooq.Table;
-import org.jooq.impl.DSL;
-
-public class TypesTable
-    implements TableFields
-{
-    private final Map<Class<?>, Table<Record>> mixinTablesCache = new ConcurrentHashMap<>();
-    private final Map<Class<?>, Table<Record>> mixinAssocsTablesCache = new ConcurrentHashMap<>();
-
-    private final Table<Record> typesTable;
-    private final SQLDialect dialect;
-    private final Schema schema;
-
-    private final JooqDslContext dsl;
-
-    TypesTable( JooqDslContext dsl, Schema schema,
-                SQLDialect dialect,
-                String typesTablesName
-              )
-    {
-        this.schema = schema;
-        this.dialect = dialect;
-        typesTable = tableOf( typesTablesName );
-        this.dsl = dsl;
-    }
-
-    static <T> Field<T> makeField( String columnName, Class<T> type )
-    {
-        return DSL.field( DSL.name( columnName ), type );
-    }
-
-    Table<Record> tableOf( String tableName )
-    {
-        return DSL.table(
-            dialect.equals( SQLDialect.SQLITE )
-            ? DSL.name( tableName )
-            : DSL.name( schema.getName(), tableName ) );
-    }
-
-    String tableNameOf( Class<?> mixinType )
-    {
-        Result<Record> typeInfo = fetchTypeInfoFromTable( mixinType );
-        if( typeInfo.isEmpty() )
-        {
-            return null;
-        }
-        return typeInfo.getValue( 0, tableNameColumn );
-    }
-
-    Table<Record> tableFor( Class<?> type, EntityDescriptor descriptor )
-    {
-        return mixinTablesCache.computeIfAbsent( type, t ->
-        {
-            String tableName = tableNameOf( t );
-            if( tableName == null )
-            {
-                Result<Record> newMixinTable = createNewMixinTable( type, descriptor );
-                return tableOf( newMixinTable.getValue( 0, tableNameColumn ) );
-            }
-            return tableOf( tableName );
-        } );
-    }
-
-    private Result<Record> fetchTypeInfoFromTable( Class<?> mixinTableName )
-    {
-        return dsl.select()
-                  .from( typesTable )
-                  .where( identityColumn.eq( mixinTableName.getName() ) )
-                  .fetch();
-    }
-
-    private Result<Record> createNewMixinTable( Class<?> mixinType, EntityDescriptor descriptor )
-    {
-        String mixinTypeName = mixinType.getName();
-        String tableName = createNewTableName( mixinType );
-        CreateTableColumnStep primaryTable = dsl.createTable( DSL.name( schema.getName(), tableName ) )
-                                                .column( identityColumn )
-                                                .column( createdColumn );
-        descriptor.state().properties().forEach(
-            property ->
-            {
-                QualifiedName qualifiedName = property.qualifiedName();
-                if( qualifiedName.type().replace( '-', '$' ).equals( mixinTypeName ) )
-                {
-                    primaryTable.column( fieldOf( property ) );
-                }
-            } );
-        descriptor.state().associations().forEach(
-            assoc ->
-            {
-                QualifiedName qualifiedName = assoc.qualifiedName();
-                if( qualifiedName.type().replace( '-', '$' ).equals( mixinTypeName ) )
-                {
-                    primaryTable.column( fieldOf( assoc ) );
-                }
-            } );
-        int result1 = primaryTable.execute();
-        int result3 = dsl.insertInto( typesTable )
-                         .set( identityColumn, mixinTypeName )
-                         .set( tableNameColumn, tableName )
-                         .set( createdColumn, new Timestamp( System.currentTimeMillis() ) )
-                         .set( modifiedColumn, new Timestamp( System.currentTimeMillis() ) )
-                         .execute();
-        return fetchTypeInfoFromTable( mixinType );
-    }
-
-    private String createNewTableName( Class<?> mixinType )
-    {
-        String typeName = mixinType.getSimpleName();
-        String postFix = "";
-        int counter = 0;
-        boolean found = false;
-        do
-        {
-            found = checkForTableNamed( typeName + postFix );
-            postFix = "_" + counter++;
-        } while( found );
-        return typeName;
-    }
-
-    private boolean checkForTableNamed( String tableName )
-    {
-        return dsl.select()
-                  .from( typesTable )
-                  .where( tableNameColumn.eq( tableName ) )
-                  .fetch().size() > 0;
-    }
-
-    private boolean isProperty( Method method )
-    {
-        return Property.class.isAssignableFrom( method.getReturnType() ) && method.getParameterCount() == 0;
-    }
-
-    Field<Object> fieldOf( PropertyDescriptor descriptor )
-    {
-        String propertyName = descriptor.qualifiedName().name();
-        return DSL.field( DSL.name( propertyName ), dataTypeOf( descriptor ) );
-    }
-
-    Field<String> fieldOf( AssociationDescriptor descriptor )
-    {
-        String propertyName = descriptor.qualifiedName().name();
-        return DSL.field( DSL.name( propertyName ), DSL.getDataType( String.class ) );
-    }
-
-    private <T> DataType<T> dataTypeOf( PropertyDescriptor property )
-    {
-        Type type = property.type();
-        Class<?> rawType = Classes.RAW_CLASS.apply( type );
-        return SqlType.getSqlDataTypeFor( rawType );
-    }
-}

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/6055b8f7/extensions/entitystore-jooq/src/main/java/org/apache/polygene/entitystore/jooq/assembly/JooqEntityStoreAssembler.java
----------------------------------------------------------------------
diff --git a/extensions/entitystore-jooq/src/main/java/org/apache/polygene/entitystore/jooq/assembly/JooqEntityStoreAssembler.java b/extensions/entitystore-jooq/src/main/java/org/apache/polygene/entitystore/jooq/assembly/JooqEntityStoreAssembler.java
deleted file mode 100644
index c251efe..0000000
--- a/extensions/entitystore-jooq/src/main/java/org/apache/polygene/entitystore/jooq/assembly/JooqEntityStoreAssembler.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 org.apache.polygene.entitystore.jooq.assembly;
-
-import org.apache.polygene.api.identity.Identity;
-import org.apache.polygene.api.identity.StringIdentity;
-import org.apache.polygene.bootstrap.Assembler;
-import org.apache.polygene.bootstrap.Assemblers;
-import org.apache.polygene.bootstrap.AssemblyException;
-import org.apache.polygene.bootstrap.ModuleAssembly;
-import org.apache.polygene.entitystore.jooq.JooqDslContext;
-import org.apache.polygene.entitystore.jooq.JooqEntityStoreConfiguration;
-import org.apache.polygene.entitystore.jooq.JooqEntityStoreService;
-import org.jooq.SQLDialect;
-import org.jooq.conf.RenderNameStyle;
-import org.jooq.conf.Settings;
-
-/**
- * JOOQ EntityStore assembly.
- */
-@SuppressWarnings( "WeakerAccess" )
-public class JooqEntityStoreAssembler extends Assemblers.VisibilityIdentityConfig<JooqEntityStoreAssembler>
-    implements Assembler
-{
-    public static final Identity DEFAULT_ENTITYSTORE_IDENTITY = new StringIdentity( "entitystore-jooq" );
-
-    @Override
-    public void assemble( ModuleAssembly module )
-    {
-        Settings settings = getSettings();
-        if( settings == null )
-        {
-            throw new AssemblyException( "Settings must not be null" );
-        }
-
-        String identity = ( hasIdentity() ? identity() : DEFAULT_ENTITYSTORE_IDENTITY ).toString();
-        module.transients( JooqDslContext.class );
-
-        module.services( JooqEntityStoreService.class )
-              .identifiedBy( identity )
-              .visibleIn( visibility() )
-              .instantiateOnStartup()
-              .setMetaInfo( settings );
-
-        if( hasConfig() )
-        {
-            configModule().entities( JooqEntityStoreConfiguration.class ).visibleIn( configVisibility() );
-        }
-    }
-
-    protected Settings getSettings()
-    {
-        return new Settings().withRenderNameStyle( RenderNameStyle.QUOTED );
-    }
-
-    protected SQLDialect getSQLDialect()
-    {
-        return SQLDialect.DEFAULT;
-    }
-}

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/6055b8f7/extensions/entitystore-jooq/src/test/java/org/apache/polygene/entitystore/jooq/JooqEntityStoreTest.java
----------------------------------------------------------------------
diff --git a/extensions/entitystore-jooq/src/test/java/org/apache/polygene/entitystore/jooq/JooqEntityStoreTest.java b/extensions/entitystore-jooq/src/test/java/org/apache/polygene/entitystore/jooq/JooqEntityStoreTest.java
deleted file mode 100644
index f36db0e..0000000
--- a/extensions/entitystore-jooq/src/test/java/org/apache/polygene/entitystore/jooq/JooqEntityStoreTest.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you 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 org.apache.polygene.entitystore.jooq;
-
-import org.apache.polygene.api.common.Visibility;
-import org.apache.polygene.bootstrap.AssemblyException;
-import org.apache.polygene.bootstrap.ModuleAssembly;
-import org.apache.polygene.entitystore.jooq.assembly.JooqEntityStoreAssembler;
-import org.apache.polygene.library.sql.assembly.DataSourceAssembler;
-import org.apache.polygene.library.sql.datasource.DataSourceConfiguration;
-import org.apache.polygene.library.sql.dbcp.DBCPDataSourceServiceAssembler;
-import org.apache.polygene.test.EntityTestAssembler;
-import org.apache.polygene.test.entity.AbstractEntityStoreTest;
-import org.jooq.SQLDialect;
-import org.junit.Rule;
-import org.junit.rules.TemporaryFolder;
-
-public class JooqEntityStoreTest extends AbstractEntityStoreTest
-{
-    @Rule
-    public final TemporaryFolder tmpDir = new TemporaryFolder();
-
-    @Override
-    // START SNIPPET: assembly
-    public void assemble( ModuleAssembly module )
-        throws AssemblyException
-    {
-        // END SNIPPET: assembly
-        super.assemble( module );
-        module.defaultServices();
-        ModuleAssembly config = module.layer().module( "config" );
-        new EntityTestAssembler().visibleIn( Visibility.module ).assemble( config );
-
-        // START SNIPPET: assembly
-        // Assemble a DataSource
-        new DataSourceAssembler()
-            .withDataSourceServiceIdentity( "datasource" )
-            .identifiedBy( "ds-mysql" )
-            .visibleIn( Visibility.module )
-            .assemble( module );
-
-        // Assemble the Apache DBCP based Service Importer
-        new DBCPDataSourceServiceAssembler()
-            .identifiedBy( "datasource" )
-            .visibleIn( Visibility.module )
-            .withConfig( config, Visibility.layer )
-            .assemble( module );
-
-        new JooqEntityStoreAssembler()
-            .withConfig( config, Visibility.layer )
-            .identifiedBy( "jooq-entitystore" )
-            .assemble( module );
-        // END SNIPPET: assembly
-
-        JooqEntityStoreConfiguration jooqDefaults = config.forMixin( JooqEntityStoreConfiguration.class )
-                                                          .setMetaInfo( SQLDialect.H2 )
-                                                          .declareDefaults();
-        jooqDefaults.entitiesTableName().set( "ENTITIES" );
-
-        DataSourceConfiguration dsDefaults = config.forMixin( DataSourceConfiguration.class ).declareDefaults();
-        dsDefaults.driver().set( org.h2.Driver.class.getName() );
-        dsDefaults.enabled().set( true );
-        dsDefaults.maxPoolSize().set( 3 );
-        dsDefaults.minPoolSize().set( 1 );
-        dsDefaults.username().set( "" );
-        dsDefaults.password().set( "" );
-        dsDefaults.url().set( "jdbc:h2:" + tmpDir.getRoot().getAbsolutePath() + "/testdb;create=true" );
-        // START SNIPPET: assembly
-    }
-    // END SNIPPET: assembly
-}

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/6055b8f7/extensions/entitystore-sql/build.gradle
----------------------------------------------------------------------
diff --git a/extensions/entitystore-sql/build.gradle b/extensions/entitystore-sql/build.gradle
new file mode 100644
index 0000000..32ceb70
--- /dev/null
+++ b/extensions/entitystore-sql/build.gradle
@@ -0,0 +1,44 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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.
+ *
+ *
+ */
+
+apply plugin: 'polygene-extension'
+
+description = "Apache Polygene™ ORM EntityStore Extension"
+
+jar { manifest { name = "Apache Polygene™ Extension - EntityStore - ORM" } }
+
+dependencies {
+  api polygene.core.bootstrap
+  api polygene.library( 'sql' )
+  api libraries.jooq
+
+  runtimeOnly polygene.core.runtime
+
+  testImplementation polygene.internals.testsupport
+  testImplementation polygene.library( 'sql-dbcp' )
+  testImplementation libraries.docker_junit
+
+  testRuntimeOnly libraries.logback
+  testRuntimeOnly libraries.derby
+  testRuntimeOnly libraries.h2
+  testRuntimeOnly libraries.mysql_connector
+  testRuntimeOnly libraries.postgres
+  testRuntimeOnly libraries.sqlite
+}

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/6055b8f7/extensions/entitystore-sql/dev-status.xml
----------------------------------------------------------------------
diff --git a/extensions/entitystore-sql/dev-status.xml b/extensions/entitystore-sql/dev-status.xml
new file mode 100644
index 0000000..b6d4c31
--- /dev/null
+++ b/extensions/entitystore-sql/dev-status.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+  ~  Licensed to the Apache Software Foundation (ASF) under one
+  ~  or more contributor license agreements.  See the NOTICE file
+  ~  distributed with this work for additional information
+  ~  regarding copyright ownership.  The ASF licenses this file
+  ~  to you 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 xmlns="http://polygene.apache.org/schemas/2008/dev-status/1"
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xsi:schemaLocation="http://polygene.apache.org/schemas/2008/dev-status/1
+        http://polygene.apache.org/schemas/2008/dev-status/1/dev-status.xsd">
+  <status>
+        <!--none,early,beta,stable,mature-->
+        <codebase>early</codebase>
+
+        <!-- none, brief, good, complete -->
+        <documentation>brief</documentation>
+
+        <!-- none, some, good, complete -->
+        <unittests>good</unittests>
+    </status>
+    <licenses>
+        <license>ALv2</license>
+    </licenses>
+</module>

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/6055b8f7/extensions/entitystore-sql/src/docs/es-sql.txt
----------------------------------------------------------------------
diff --git a/extensions/entitystore-sql/src/docs/es-sql.txt b/extensions/entitystore-sql/src/docs/es-sql.txt
new file mode 100644
index 0000000..a36d463
--- /dev/null
+++ b/extensions/entitystore-sql/src/docs/es-sql.txt
@@ -0,0 +1,58 @@
+///////////////////////////////////////////////////////////////
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+///////////////////////////////////////////////////////////////
+
+[[extension-es-jooq,SQL EntityStore]]
+= ORM EntityStore =
+
+[devstatus]
+--------------
+source=extensions/entitystore-jooq/dev-status.xml
+--------------
+
+This entitystore is backed by a SQL server, and maps each mixin type of the Composite into separate tables. This is more
+enterprise-friendly, but comes at the cost of less performance compared to the <<extension-es-sql>>.
+
+This extension fully leverage the <<library-sql>> meaning that you must use it to assemble your DataSource and that you
+get <<library-circuitbreaker,Circuit Breaker>> and <<library-jmx, JMX>> integration for free.
+
+include::../../build/docs/buildinfo/artifact.txt[]
+
+== Assembly ==
+
+Assembly is done using the provided Assembler:
+
+[snippet,java]
+----
+source=extensions/entitystore-jooq/src/test/java/org/apache/polygene/entitystore/jooq/JooqEntityStoreTest.java
+tag=assembly
+----
+
+== Configuration ==
+
+Here are the available configuration properties:
+
+[snippet,java]
+----
+source=extensions/entitystore-jooq/src/main/java/org/apache/polygene/entitystore/jooq/JooqEntityStoreConfiguration.java
+tag=config
+----
+
+All authentication related properties are optional.
+By default no authentication is used.
+As soon as you provide a `username`, authentication is set up.

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/6055b8f7/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/AssociationValue.java
----------------------------------------------------------------------
diff --git a/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/AssociationValue.java b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/AssociationValue.java
new file mode 100644
index 0000000..9fb6771
--- /dev/null
+++ b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/AssociationValue.java
@@ -0,0 +1,28 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.apache.polygene.entitystore.sql;
+
+import org.apache.polygene.api.common.QualifiedName;
+
+@SuppressWarnings( "WeakerAccess" )
+public class AssociationValue
+{
+    QualifiedName name;
+    String position;
+    String reference;
+}

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/6055b8f7/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/BaseEntity.java
----------------------------------------------------------------------
diff --git a/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/BaseEntity.java b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/BaseEntity.java
new file mode 100644
index 0000000..982366d
--- /dev/null
+++ b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/BaseEntity.java
@@ -0,0 +1,34 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.apache.polygene.entitystore.sql;
+
+import java.time.Instant;
+import org.apache.polygene.api.entity.EntityDescriptor;
+import org.apache.polygene.api.identity.Identity;
+
+@SuppressWarnings( "WeakerAccess" )
+public class BaseEntity
+{
+    EntityDescriptor type;
+    Identity identity;
+    String version;
+    String applicationVersion;
+    Instant modifedAt;
+    Instant createdAt;
+    Identity currentValueIdentity;
+}

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/6055b8f7/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/EntitiesTable.java
----------------------------------------------------------------------
diff --git a/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/EntitiesTable.java b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/EntitiesTable.java
new file mode 100644
index 0000000..d25e7fc
--- /dev/null
+++ b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/EntitiesTable.java
@@ -0,0 +1,363 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.apache.polygene.entitystore.sql;
+
+import java.lang.reflect.Method;
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.polygene.api.association.AssociationDescriptor;
+import org.apache.polygene.api.common.QualifiedName;
+import org.apache.polygene.api.composite.Composite;
+import org.apache.polygene.api.entity.EntityComposite;
+import org.apache.polygene.api.entity.EntityDescriptor;
+import org.apache.polygene.api.entity.EntityReference;
+import org.apache.polygene.api.identity.HasIdentity;
+import org.apache.polygene.api.identity.StringIdentity;
+import org.apache.polygene.api.property.PropertyDescriptor;
+import org.apache.polygene.api.structure.ModuleDescriptor;
+import org.apache.polygene.api.type.EntityCompositeType;
+import org.apache.polygene.api.unitofwork.NoSuchEntityTypeException;
+import org.apache.polygene.api.util.Classes;
+import org.apache.polygene.spi.entitystore.EntityNotFoundException;
+import org.apache.polygene.spi.entitystore.EntityStoreUnitOfWork;
+import org.apache.polygene.spi.entitystore.helpers.DefaultEntityState;
+import org.jooq.Condition;
+import org.jooq.Field;
+import org.jooq.Record;
+import org.jooq.Result;
+import org.jooq.Schema;
+import org.jooq.SelectJoinStep;
+import org.jooq.SelectQuery;
+import org.jooq.Table;
+import org.jooq.impl.DSL;
+
+@SuppressWarnings( "WeakerAccess" )
+public class EntitiesTable
+    implements TableFields
+{
+    private static final Predicate<? super Class<?>> NOT_COMPOSITE = type -> !( type.equals( Composite.class ) || type.equals( EntityComposite.class ) );
+    private static final Predicate<? super Class<?>> NOT_HASIDENTITY = type -> !( type.equals( HasIdentity.class ) );
+    private Map<EntityCompositeType, Set<Class<?>>> mixinTypeCache = new ConcurrentHashMap<>();
+    private Map<Class<?>, MixinTable> mixinTablesCache = new ConcurrentHashMap<>();
+
+    private final Table<Record> entitiesTable;
+    private JooqDslContext dsl;
+    private final TypesTable types;
+    private final Schema schema;
+    private String applicationVersion;
+    private boolean replacementStrategy = false;  // Figure out later if we should support both and if so, how.
+
+    EntitiesTable( JooqDslContext dsl, Schema schema, TypesTable types, String applicationVersion, String entitiesTableName )
+    {
+        this.dsl = dsl;
+        this.types = types;
+        this.schema = schema;
+        this.applicationVersion = applicationVersion;
+        entitiesTable = types.tableOf( entitiesTableName );
+    }
+
+    public BaseEntity fetchEntity( EntityReference reference, ModuleDescriptor module )
+    {
+
+        Result<Record> baseEntityResult = dsl
+            .selectFrom( entitiesTable )
+            .where( identityColumn.eq( reference.identity().toString() ) )
+            .fetch();
+
+        if( baseEntityResult.isEmpty() )
+        {
+            throw new EntityNotFoundException( reference );
+        }
+        Record row = baseEntityResult.get( 0 );
+        return toBaseEntity( row, module );
+    }
+
+    protected BaseEntity toBaseEntity( Record row, ModuleDescriptor module )
+    {
+        BaseEntity result = new BaseEntity();
+        String typeName = row.field( typeNameColumn ).get( row );
+        result.type = findEntityDescriptor( typeName, module );
+        result.version = row.field( versionColumn ).get( row );
+        result.applicationVersion = row.field( applicationVersionColumn ).get( row );
+        result.identity = StringIdentity.identityOf( row.field( identityColumn ).get( row ) );
+        result.currentValueIdentity = EntityReference.parseEntityReference( row.field( valueIdentityColumn ).get( row ) ).identity();
+        result.modifedAt = Instant.ofEpochMilli( row.field( modifiedColumn ).get( row ).getTime() );
+        result.createdAt = Instant.ofEpochMilli( row.field( createdColumn ).get( row ).getTime() );
+        return result;
+    }
+
+    public Stream<BaseEntity> fetchAll( EntityDescriptor type, ModuleDescriptor module )
+    {
+        Result<Record> baseEntityResult = dsl
+            .selectFrom( entitiesTable )
+            .fetch();
+        return baseEntityResult.stream().map( record -> toBaseEntity( record, module ) );
+    }
+
+    private EntityDescriptor findEntityDescriptor( String typeName, ModuleDescriptor module )
+    {
+        try
+        {
+            Class<?> type = getClass().getClassLoader().loadClass( typeName );
+            return module.typeLookup().lookupEntityModel( type );
+        }
+        catch( ClassNotFoundException e )
+        {
+            throw new NoSuchEntityTypeException( typeName, module);
+        }
+    }
+
+    void insertEntity( DefaultEntityState state, BaseEntity baseEntity )
+    {
+        EntityCompositeType compositeType = state.entityDescriptor().valueType();
+        Set<Class<?>> mixinTypes = mixinTypeCache.computeIfAbsent( compositeType, createMixinTypesSet( compositeType ) );
+        mixinTypes.forEach( type ->
+                            {
+                                MixinTable table = findMixinTable( type, state.entityDescriptor() );
+                                table.insertMixinState( state, baseEntity.currentValueIdentity.toString() );
+                            } );
+    }
+
+    void modifyEntity( DefaultEntityState state, BaseEntity baseEntity, EntityStoreUnitOfWork uow )
+    {
+        updateBaseEntity( baseEntity, uow );
+        if( replacementStrategy )
+        {
+            insertEntity( state, baseEntity );      // replacement strategy (more safe)
+        }
+        else
+        {
+            EntityCompositeType compositeType = state.entityDescriptor().valueType();
+            Set<Class<?>> mixinTypes = mixinTypeCache.computeIfAbsent( compositeType, createMixinTypesSet( compositeType ) );
+            mixinTypes.forEach( type ->
+                                {
+                                    MixinTable table = findMixinTable( type, state.entityDescriptor() );
+                                    table.modifyMixinState( state, baseEntity.currentValueIdentity.toString() );
+                                } );
+        }
+    }
+
+    private MixinTable findMixinTable( Class<?> type, EntityDescriptor entityDescriptor )
+    {
+        return mixinTablesCache.computeIfAbsent( type, t -> new MixinTable( dsl, schema, types, type, entityDescriptor ) );
+    }
+
+    private Set<Class<?>> mixinsOf( Stream<? extends AssociationDescriptor> stream )
+    {
+        return stream
+            .map( AssociationDescriptor::accessor )
+            .filter( Classes.instanceOf( Method.class ) )
+            .map( accessor -> (Method) accessor )
+            .map( Method::getDeclaringClass )
+            .filter( NOT_HASIDENTITY )
+            .filter( NOT_COMPOSITE )
+            .collect( Collectors.toSet() );
+    }
+
+    private Function<EntityCompositeType, Set<Class<?>>> createMixinTypesSet( EntityCompositeType compositeType )
+    {
+        return type ->
+        {
+            Set<Class<?>> mixins = compositeType
+                .properties()
+                .map( PropertyDescriptor::accessor )
+                .filter( Classes.instanceOf( Method.class ) )
+                .map( accessor -> (Method) accessor )
+                .map( Method::getDeclaringClass )
+                .filter( NOT_HASIDENTITY )
+                .filter( NOT_COMPOSITE )
+                .collect( Collectors.toSet() );
+            Set<Class<?>> mixinsWithAssociations = mixinsOf( compositeType.associations() );
+            Set<Class<?>> mixinsWithManyAssociations = mixinsOf( compositeType.manyAssociations() );
+            Set<Class<?>> mixinsWithNamedAssociations = mixinsOf( compositeType.namedAssociations() );
+            mixins.addAll( mixinsWithAssociations );
+            mixins.addAll( mixinsWithManyAssociations );
+            mixins.addAll( mixinsWithNamedAssociations );
+            return mixins;
+        };
+    }
+
+    void createNewBaseEntity( EntityReference reference, EntityDescriptor descriptor, EntityStoreUnitOfWork uow )
+    {
+        String valueIdentity = UUID.randomUUID().toString();
+        dsl.insertInto( entitiesTable )
+           .set( identityColumn, reference.identity().toString() )
+           .set( createdColumn, new Timestamp( uow.currentTime().toEpochMilli() ) )
+           .set( modifiedColumn, new Timestamp( uow.currentTime().toEpochMilli() ) )
+           .set( valueIdentityColumn, valueIdentity )
+           .set( typeNameColumn, descriptor.primaryType().getName() )
+           .set( versionColumn, "1" )
+           .set( applicationVersionColumn, applicationVersion )
+           .execute();
+    }
+
+    private void updateBaseEntity( BaseEntity entity, EntityStoreUnitOfWork uow )
+    {
+        entity.version = increment( entity.version );
+        if( replacementStrategy )
+        {
+            entity.currentValueIdentity = StringIdentity.identityOf( UUID.randomUUID().toString() );
+        }
+        dsl.update( entitiesTable )
+           .set( modifiedColumn, new Timestamp( uow.currentTime().toEpochMilli() ) )
+           .set( valueIdentityColumn, entity.currentValueIdentity.toString() )
+           .set( versionColumn, entity.version )
+           .set( applicationVersionColumn, applicationVersion )
+           .execute();
+    }
+
+    private String increment( String version )
+    {
+        long ver = Long.parseLong( version );
+        return Long.toString( ver + 1 );
+    }
+
+    /**
+     * Builds the SELECT statement for a given entity.
+     * <p>
+     * Example; If we have the following entity
+     * </p>
+     * <code><pre>
+     *     public interface LegalEntity
+     *     {
+     *         Property&lt;String&gt; registration();
+     *     }
+     * <p>
+     *     public interface Person extends LegalEntity
+     *     {
+     *         Property&lt;String&gt; name();
+     * <p>
+     *         &#64;Optional
+     *         Association&lt;Person&gt; spouse();
+     * <p>
+     *         ManyAssocation&lt;Person&gt; children();
+     *     }
+     * </pre></code>
+     * <p>
+     * and we do a simple;
+     * <code><pre>
+     *     Person p = uow.get( Person.class, "niclas" );
+     * </pre></code>
+     * <p>
+     * then the generated query will be
+     * </p>
+     * <code><pre>
+     *     SELECT * FROM ENTITIES
+     *     JOIN Person ON identity = ENTITIES.value_id
+     *     JOIN LegalEntity ON identity = ENTITIES.value_id
+     *     JOIN Person_Assoc ON identity = ENTITIES.value_id
+     *     WHERE ENTITIES.identity = '123'
+     * </pre></code>
+     *
+     * @param entityDescriptor The descriptor of the entity type to be built.
+     * @return The SELECT query that can be executed to retrieve the entity.
+     */
+    public SelectQuery<Record> createGetEntityQuery( EntityDescriptor entityDescriptor, EntityReference reference )
+    {
+        List<Table<Record>> joins = getMixinTables( entityDescriptor );
+        SelectJoinStep<Record> from = dsl.select().from( entitiesTable );
+        for( Table<Record> joinedTable : joins )
+        {
+            Condition joinCondition = valueIdentityColumn.eq( identityColumnOf( joinedTable ) );
+            from = from.leftJoin( joinedTable ).on( joinCondition );
+        }
+        return from.where( identityColumnOf( entitiesTable ).eq( reference.identity().toString() ) ).getQuery();
+    }
+
+    public void fetchAssociations( BaseEntity entity, EntityDescriptor entityDescriptor, Consumer<AssociationValue> consume )
+    {
+        List<Table<Record>> joins = getAssocationsTables( entityDescriptor );
+        SelectJoinStep<Record> from = dsl.select().from( entitiesTable );
+        for( Table<Record> joinedTable : joins )
+        {
+            Condition joinCondition = valueIdentityColumn.eq( identityColumnOf( joinedTable ) );
+            from = from.join( joinedTable ).on( joinCondition );
+        }
+        String reference = entity.identity.toString();
+        SelectQuery<Record> query = from.where( identityColumnOf( entitiesTable ).eq( reference ) ).getQuery();
+        Result<Record> result = query.fetch();
+        result.forEach( record ->
+                        {
+                            AssociationValue value = new AssociationValue();
+                            value.name = QualifiedName.fromClass( entityDescriptor.primaryType(), record.getValue( nameColumn ) );
+                            value.position = record.getValue( indexColumn );
+                            value.reference = record.getValue( referenceColumn );
+                            consume.accept( value );
+                        } );
+    }
+
+    private Field<String> identityColumnOf( Table<Record> joinedTable )
+    {
+        return DSL.field( DSL.name( joinedTable.getName(), identityColumn.getName() ), String.class );
+    }
+
+    public List<Table<Record>> getMixinTables( EntityDescriptor entityDescriptor )
+    {
+        return entityDescriptor
+            .mixinTypes()
+            .filter( NOT_COMPOSITE )
+            .filter( NOT_HASIDENTITY )
+            .map( ( Class<?> type ) -> types.tableFor( type, entityDescriptor ) )
+            .collect( Collectors.toList() );
+    }
+
+    public List<Table<Record>> getAssocationsTables( EntityDescriptor entityDescriptor )
+    {
+        return entityDescriptor
+            .mixinTypes()
+            .filter( NOT_COMPOSITE )
+            .filter( NOT_HASIDENTITY )
+            .map( type -> findMixinTable( type, entityDescriptor ) )
+            .map( MixinTable::associationsTable )
+            .collect( Collectors.toList() );
+    }
+
+    public void removeEntity( EntityReference entityReference, EntityDescriptor descriptor )
+    {
+        ModuleDescriptor module = descriptor.module();
+        BaseEntity baseEntity = fetchEntity( entityReference, module );
+        if( replacementStrategy )
+        {
+            // TODO;  Mark deleted, I guess... not implemented
+        }
+        else
+        {
+            dsl.delete( entitiesTable )
+               .where(
+                   identityColumnOf( entitiesTable ).eq( entityReference.identity().toString() )
+                     )
+               .execute()
+            ;
+            String valueId = baseEntity.currentValueIdentity.toString();
+            List<Table<Record>> mixinTables = getMixinTables( descriptor );
+            List<Table<Record>> assocTables = getAssocationsTables( descriptor );
+            mixinTables.forEach( table -> dsl.delete( table ).where( identityColumnOf( table ).eq( valueId ) ).execute() );
+            assocTables.forEach( table -> dsl.delete( table ).where( identityColumnOf( table ).eq( valueId ) ).execute() );
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/6055b8f7/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/JooqDslContext.java
----------------------------------------------------------------------
diff --git a/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/JooqDslContext.java b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/JooqDslContext.java
new file mode 100644
index 0000000..d89c058
--- /dev/null
+++ b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/JooqDslContext.java
@@ -0,0 +1,58 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.apache.polygene.entitystore.sql;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import javax.sql.DataSource;
+import org.apache.polygene.api.injection.scope.Service;
+import org.apache.polygene.api.injection.scope.Uses;
+import org.apache.polygene.api.mixin.Mixins;
+import org.jooq.Configuration;
+import org.jooq.DSLContext;
+import org.jooq.SQLDialect;
+import org.jooq.conf.Settings;
+import org.jooq.impl.DSL;
+import org.jooq.impl.DefaultConfiguration;
+
+@Mixins( JooqDslContext.Mixin.class )
+public interface JooqDslContext extends DSLContext
+{
+
+    class Mixin
+        implements InvocationHandler
+    {
+        private DSLContext dsl;
+
+        public Mixin( @Service DataSource dataSource, @Uses Settings settings, @Uses SQLDialect dialect )
+        {
+            Configuration configuration = new DefaultConfiguration()
+                .set( dataSource )
+                .set( dialect )
+                .set( settings );
+            dsl = DSL.using( configuration );
+        }
+
+        @Override
+        public Object invoke( Object o, Method method, Object[] objects )
+            throws Throwable
+        {
+            return method.invoke( dsl, objects );       // delegate all
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/6055b8f7/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/MixinTable.java
----------------------------------------------------------------------
diff --git a/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/MixinTable.java b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/MixinTable.java
new file mode 100644
index 0000000..80c26a2
--- /dev/null
+++ b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/MixinTable.java
@@ -0,0 +1,256 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.apache.polygene.entitystore.sql;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Method;
+import java.sql.Timestamp;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import org.apache.polygene.api.association.AssociationDescriptor;
+import org.apache.polygene.api.common.QualifiedName;
+import org.apache.polygene.api.entity.EntityDescriptor;
+import org.apache.polygene.api.entity.EntityReference;
+import org.apache.polygene.api.property.PropertyDescriptor;
+import org.apache.polygene.spi.entity.ManyAssociationState;
+import org.apache.polygene.spi.entity.NamedAssociationState;
+import org.apache.polygene.spi.entitystore.helpers.DefaultEntityState;
+import org.jooq.Field;
+import org.jooq.InsertSetMoreStep;
+import org.jooq.InsertSetStep;
+import org.jooq.Name;
+import org.jooq.Record;
+import org.jooq.Schema;
+import org.jooq.Table;
+import org.jooq.UpdateSetMoreStep;
+import org.jooq.impl.DSL;
+
+class MixinTable
+    implements TableFields
+{
+
+    private final Table<Record> mixinTable;
+    private final Table<Record> mixinAssocsTable;
+
+    private final JooqDslContext dsl;
+    private final Map<QualifiedName, Field<Object>> properties = new ConcurrentHashMap<>();
+    private final Map<QualifiedName, Field<String>> associations = new ConcurrentHashMap<>();
+    private final List<QualifiedName> manyAssociations = new CopyOnWriteArrayList<>();
+    private final List<QualifiedName> namedAssociations = new CopyOnWriteArrayList<>();
+
+    private TypesTable types;
+    private final Class<?> mixinType;
+
+    MixinTable( JooqDslContext dsl, Schema schema, TypesTable types, Class<?> mixinType,
+                EntityDescriptor descriptor )
+    {
+        this.dsl = dsl;
+        this.types = types;
+        this.mixinType = mixinType;
+        mixinTable = types.tableFor( mixinType, descriptor );
+        mixinAssocsTable = getAssocsTable( descriptor, schema );
+
+        descriptor.valueType().properties()
+                  .filter( this::isThisMixin )
+                  .forEach( propDescriptor ->
+                            {
+                                QualifiedName propertyName = propDescriptor.qualifiedName();
+                                Field<Object> propertyField = types.fieldOf( propDescriptor );
+                                properties.put( propertyName, propertyField );
+                            }
+                          );
+
+        descriptor.valueType().associations()
+                  .filter( this::isThisMixin )
+                  .forEach( assocDescriptor ->
+                            {
+                                QualifiedName assocName = assocDescriptor.qualifiedName();
+                                Field<String> assocField = types.fieldOf( assocDescriptor );
+                                associations.put( assocName, assocField );
+                            }
+                          );
+
+        descriptor.valueType().manyAssociations()
+                  .filter( this::isThisMixin )
+                  .forEach( assocDescriptor -> manyAssociations.add( assocDescriptor.qualifiedName() ) );
+
+        descriptor.valueType().namedAssociations()
+                  .filter( this::isThisMixin )
+                  .forEach( assocDescriptor -> namedAssociations.add( assocDescriptor.qualifiedName() ) );
+    }
+
+    void insertMixinState( DefaultEntityState state, String valueIdentity )
+    {
+        InsertSetMoreStep<Record> primaryTable =
+            dsl.insertInto( mixinTable )
+               .set( identityColumn, valueIdentity )
+               .set( createdColumn, new Timestamp( System.currentTimeMillis() ) );
+
+        properties.forEach( ( propertyName, propertyField ) -> primaryTable.set( propertyField, state.propertyValueOf( propertyName ) ) );
+        associations.forEach( ( assocName, assocField ) ->
+                              {
+                                  EntityReference reference = state.associationValueOf( assocName );
+                                  String identity = null;
+                                  if( reference != null )
+                                  {
+                                      identity = reference.identity().toString();
+                                  }
+                                  primaryTable.set( assocField, identity );
+                              }
+                            );
+        int result = primaryTable.execute();
+
+        if( mixinAssocsTable != null )
+        {
+            insertManyAndNamedAssociations( state, valueIdentity );
+        }
+    }
+
+    private void insertManyAndNamedAssociations( DefaultEntityState state, String valueIdentity )
+    {
+        manyAssociations.forEach( assocName ->
+                                  {
+                                      InsertSetStep<Record> assocsTable = dsl.insertInto( mixinAssocsTable );
+                                      ManyAssociationState entityReferences = state.manyAssociationValueOf( assocName );
+                                      int endCount = entityReferences.count();
+                                      int counter = 0;
+                                      for( EntityReference ref : entityReferences )
+                                      {
+                                          InsertSetMoreStep<Record> set = assocsTable.set( identityColumn, valueIdentity )
+                                                                                     .set( nameColumn, assocName.name() )
+                                                                                     .set( indexColumn, "" + counter++ )
+                                                                                     .set( referenceColumn, ref == null ? null : ref.identity().toString() );
+                                          if( ++counter < endCount )
+                                          {
+                                              set.newRecord();
+                                          }
+                                      }
+                                      InsertSetMoreStep<Record> assocs = assocsTable.set( Collections.emptyMap() );
+                                      assocs.execute();
+                                  } );
+
+        namedAssociations.forEach( assocName ->
+                                   {
+                                       InsertSetStep<Record> assocsTable = dsl.insertInto( mixinAssocsTable );
+                                       NamedAssociationState entityReferences = state.namedAssociationValueOf( assocName );
+                                       int count = entityReferences.count();
+                                       for( String name : entityReferences )
+                                       {
+                                           EntityReference ref = entityReferences.get( name );
+                                           InsertSetMoreStep<Record> set = assocsTable.set( identityColumn, valueIdentity )
+                                                                                      .set( nameColumn, assocName.name() )
+                                                                                      .set( indexColumn, name )
+                                                                                      .set( referenceColumn, ref.identity().toString() );
+                                           if( --count > 0 )
+                                           {
+                                               set.newRecord();
+                                           }
+                                       }
+                                       InsertSetMoreStep<Record> assocs = assocsTable.set( Collections.emptyMap() );
+                                       assocs.execute();
+                                   } );
+    }
+
+    Table<Record> associationsTable()
+    {
+        return mixinAssocsTable;
+    }
+
+    private boolean isThisMixin( PropertyDescriptor descriptor )
+    {
+        Class<?> declaringClass = declaredIn( descriptor );
+        return mixinType.equals( declaringClass );
+    }
+
+    private boolean isThisMixin( AssociationDescriptor descriptor )
+    {
+        Class<?> declaringClass = declaredIn( descriptor );
+        return mixinType.equals( declaringClass );
+    }
+
+    private Class<?> declaredIn( PropertyDescriptor descriptor )
+    {
+        AccessibleObject accessor = descriptor.accessor();
+        if( accessor instanceof Method )
+        {
+            return ( (Method) accessor ).getDeclaringClass();
+        }
+        throw new UnsupportedOperationException( "Property declared as " + accessor.getClass() + " is not supported in this Entity Store yet." );
+    }
+
+    private Class<?> declaredIn( AssociationDescriptor descriptor )
+    {
+        AccessibleObject accessor = descriptor.accessor();
+        if( accessor instanceof Method )
+        {
+            return ( (Method) accessor ).getDeclaringClass();
+        }
+        throw new UnsupportedOperationException( "Property declared as " + accessor.getClass() + " is not supported in this Entity Store yet." );
+    }
+
+    void modifyMixinState( DefaultEntityState state, String valueId )
+    {
+        UpdateSetMoreStep<Record> primaryTable =
+            dsl.update( mixinTable )
+               .set( Collections.emptyMap() );  // empty map is a hack to get the right type returned from JOOQ.
+
+        properties.forEach( ( propertyName, propertyField ) -> primaryTable.set( propertyField, state.propertyValueOf( propertyName ) ) );
+        associations.forEach( ( assocName, assocField ) ->
+                              {
+                                  EntityReference reference = state.associationValueOf( assocName );
+                                  primaryTable.set( assocField,
+                                                    reference == null ? null : reference.identity().toString()
+                                                  );
+                              }
+                            );
+        int result = primaryTable.execute();
+
+        if( mixinAssocsTable != null )
+        {
+            // Need to remove existing records.
+            dsl.delete( mixinAssocsTable )
+               .where( identityColumn.eq( valueId ) )
+               .execute();
+            insertManyAndNamedAssociations( state, valueId );
+        }
+    }
+
+    private Table<Record> getAssocsTable( EntityDescriptor descriptor, Schema schema )
+    {
+        if( descriptor.state().manyAssociations().count() > 0
+            || descriptor.state().namedAssociations().count() > 0 )
+        {
+            Name tableName = DSL.name( schema.getName(), mixinTable.getName() + ASSOCS_TABLE_POSTFIX );
+            Table<Record> table = DSL.table( tableName );
+            int result2 = dsl.createTableIfNotExists( table )
+                             .column( identityColumn )
+                             .column( nameColumn )
+                             .column( indexColumn )
+                             .column( referenceColumn )
+                             .execute();
+            return table;
+        }
+        else
+        {
+            return null;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/6055b8f7/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlEntityStoreConfiguration.java
----------------------------------------------------------------------
diff --git a/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlEntityStoreConfiguration.java b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlEntityStoreConfiguration.java
new file mode 100644
index 0000000..e93d7a3
--- /dev/null
+++ b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlEntityStoreConfiguration.java
@@ -0,0 +1,73 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.apache.polygene.entitystore.sql;
+
+import org.apache.polygene.api.common.UseDefaults;
+import org.apache.polygene.api.property.Property;
+import org.apache.polygene.library.sql.common.SQLConfiguration;
+
+// START SNIPPET: config
+public interface SqlEntityStoreConfiguration extends SQLConfiguration
+{
+    /**
+     * Name of the database schema to use.
+     * Ignored on SQL databases that don't support schemas.
+     */
+    @UseDefaults( "POLYGENE" )
+    @Override
+    Property<String> schemaName();
+
+    /**
+     * Name of the entities table.
+     * <p>
+     * This table contains the Identity and other metadata about each entity instance
+     * </p>
+     */
+    @UseDefaults( "ENTITIES" )
+    Property<String> entitiesTableName();
+
+    /**
+     * Name of the entity types table.
+     * <p>
+     * This table contains the metainfo about each type. Types are versioned according to
+     * application version, to support entity migration over time, and therefor there might
+     * be (but not necessarily) multiple tables for entity types that has evolved beyond
+     * what can be managed within a single table.
+     * </p>
+     */
+    @UseDefaults( "TYPES" )
+    Property<String> typesTableName();
+
+    /**
+     * Defines whether the database schema and table should be created if not already present.
+     */
+    @UseDefaults( "true" )
+    Property<Boolean> createIfMissing();
+
+    /**
+     * The SQL dialect that is being used.
+     * <p>
+     * Typically that is matching a supporting dialect in JOOQ.
+     * See {@link org.jooq.SQLDialect} for supported values.
+     * </p>
+     * @return The property with the dialect value.
+     */
+    @UseDefaults( "" )
+    Property<String> dialect();
+}
+// END SNIPPET: config

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/6055b8f7/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlEntityStoreMixin.java
----------------------------------------------------------------------
diff --git a/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlEntityStoreMixin.java b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlEntityStoreMixin.java
new file mode 100644
index 0000000..5f17fd4
--- /dev/null
+++ b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlEntityStoreMixin.java
@@ -0,0 +1,367 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.apache.polygene.entitystore.sql;
+
+import java.sql.Timestamp;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.OffsetDateTime;
+import java.time.Period;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+import org.apache.polygene.api.association.AssociationDescriptor;
+import org.apache.polygene.api.association.AssociationStateDescriptor;
+import org.apache.polygene.api.common.QualifiedName;
+import org.apache.polygene.api.entity.EntityDescriptor;
+import org.apache.polygene.api.entity.EntityReference;
+import org.apache.polygene.api.identity.HasIdentity;
+import org.apache.polygene.api.identity.IdentityGenerator;
+import org.apache.polygene.api.injection.scope.Service;
+import org.apache.polygene.api.injection.scope.This;
+import org.apache.polygene.api.serialization.Serialization;
+import org.apache.polygene.api.structure.ModuleDescriptor;
+import org.apache.polygene.api.type.ValueType;
+import org.apache.polygene.api.usecase.Usecase;
+import org.apache.polygene.spi.entity.EntityState;
+import org.apache.polygene.spi.entity.EntityStatus;
+import org.apache.polygene.spi.entitystore.DefaultEntityStoreUnitOfWork;
+import org.apache.polygene.spi.entitystore.EntityNotFoundException;
+import org.apache.polygene.spi.entitystore.EntityStore;
+import org.apache.polygene.spi.entitystore.EntityStoreSPI;
+import org.apache.polygene.spi.entitystore.EntityStoreUnitOfWork;
+import org.apache.polygene.spi.entitystore.StateCommitter;
+import org.apache.polygene.spi.entitystore.helpers.DefaultEntityState;
+import org.jooq.Record;
+import org.jooq.Result;
+import org.jooq.SelectQuery;
+
+import static org.apache.polygene.api.entity.EntityReference.parseEntityReference;
+
+public class SqlEntityStoreMixin
+    implements EntityStore, EntityStoreSPI
+{
+    @This
+    private SqlTable sqlTable;
+
+    @Service
+    private IdentityGenerator identityGenerator;
+
+    @Service
+    private Serialization serialization;
+
+    @Override
+    public EntityState newEntityState( EntityStoreUnitOfWork unitOfWork, EntityReference reference, EntityDescriptor entityDescriptor )
+    {
+        return new DefaultEntityState( unitOfWork.currentTime(), reference, entityDescriptor );
+    }
+
+    @Override
+    public EntityState entityStateOf( EntityStoreUnitOfWork unitOfWork, ModuleDescriptor module, EntityReference reference )
+    {
+        BaseEntity baseEntity = sqlTable.fetchBaseEntity( reference, module );
+        SelectQuery<Record> selectQuery = sqlTable.createGetEntityQuery( baseEntity.type, reference );
+        Result<Record> result = selectQuery.fetch();
+        if( result.isEmpty() )
+        {
+            throw new EntityNotFoundException( reference );
+        }
+        return toEntityState( result, baseEntity, reference, module );
+    }
+
+    protected EntityState toEntityState( Result<Record> result, BaseEntity baseEntity, EntityReference reference, ModuleDescriptor module )
+    {
+        AssociationStateDescriptor stateDescriptor = baseEntity.type.state();
+        Map<QualifiedName, Object> properties = new HashMap<>();
+        properties.put( HasIdentity.IDENTITY_STATE_NAME, baseEntity.identity );
+        stateDescriptor.properties()
+                       .filter( prop -> !HasIdentity.IDENTITY_STATE_NAME.equals( prop.qualifiedName() ) )
+                       .forEach( prop ->
+                                 {
+                                     QualifiedName qualifiedName = prop.qualifiedName();
+                                     Object value = result.getValue( 0, qualifiedName.name() );
+                                     value = amendValue( value, prop.valueType(), module );
+                                     properties.put( qualifiedName, value );
+                                 } );
+        Map<QualifiedName, EntityReference> assocations = new HashMap<>();
+        stateDescriptor.associations()
+                       .forEach( assoc ->
+                                 {
+                                     QualifiedName qualifiedName = assoc.qualifiedName();
+                                     String value = (String) result.getValue( 0, qualifiedName.name() );
+                                     if( value != null )
+                                     {
+                                         assocations.put( qualifiedName, parseEntityReference( value ) );
+                                     }
+                                 } );
+        Map<QualifiedName, List<EntityReference>> manyAssocs = new HashMap<>();
+        Map<QualifiedName, Map<String, EntityReference>> namedAssocs = new HashMap<>();
+        sqlTable.fetchAssociations( baseEntity, baseEntity.type, associationValue ->
+        {
+            if( stateDescriptor.hasManyAssociation( associationValue.name ) )
+            {
+                addManyAssociation( stateDescriptor, manyAssocs, associationValue );
+            }
+            else if( stateDescriptor.hasNamedAssociation( associationValue.name ) )
+            {
+                addNamedAssociation( stateDescriptor, namedAssocs, associationValue );
+            }
+        } );
+
+        return new DefaultEntityState( baseEntity.version,
+                                       baseEntity.modifedAt,
+                                       reference,
+                                       EntityStatus.LOADED,
+                                       baseEntity.type,
+                                       properties,
+                                       assocations,
+                                       manyAssocs,
+                                       namedAssocs );
+    }
+
+    private Object amendValue( Object value, ValueType type, ModuleDescriptor module )
+    {
+        if( value == null )
+        {
+            return null;
+        }
+        if( value.getClass().isPrimitive() )
+        {
+            return value;
+        }
+        if( type.equals( ValueType.STRING )
+            || type.equals( ValueType.INTEGER )
+            || type.equals( ValueType.BOOLEAN )
+            || type.equals( ValueType.DOUBLE )
+            || type.equals( ValueType.IDENTITY )
+            || type.equals( ValueType.LONG )
+            || type.equals( ValueType.FLOAT )
+            || type.equals( ValueType.BYTE )
+            || type.equals( ValueType.CHARACTER )
+            || type.equals( ValueType.ENTITY_REFERENCE )
+            || type.equals( ValueType.SHORT )
+            || type.equals( ValueType.BIG_INTEGER )
+            || type.equals( ValueType.BIG_DECIMAL )
+            )
+        {
+            return value;
+        }
+        if( type.equals( ValueType.INSTANT ) )  // Instant type contains timezone (why?), and we promise to always return in UTC (or is that just bad testcases, and that we actually promise to return original instant timezone?).
+        {
+            if( value instanceof Instant )
+            {
+                return Instant.ofEpochMilli( ( (Instant) value ).toEpochMilli() );
+            }
+            if( value instanceof OffsetDateTime )
+            {
+                return Instant.ofEpochMilli( ( (OffsetDateTime) value ).toInstant().toEpochMilli() );
+            }
+            if( value instanceof ZonedDateTime )
+            {
+                return Instant.ofEpochMilli( ( (ZonedDateTime) value ).toInstant().toEpochMilli() );
+            }
+        }
+        if( type.equals( ValueType.ZONED_DATE_TIME ) )
+        {
+            if( value instanceof ZonedDateTime )
+            {
+                return ( (ZonedDateTime) value ).withZoneSameInstant( ZoneOffset.UTC );
+            }
+            if( value instanceof OffsetDateTime )
+            {
+                return ( (OffsetDateTime) value ).toZonedDateTime().withZoneSameInstant( ZoneOffset.UTC );
+            }
+        }
+        if( type.equals( ValueType.OFFSET_DATE_TIME ) )
+        {
+            if( value instanceof OffsetDateTime )
+            {
+                return ( (OffsetDateTime) value ).withOffsetSameInstant( ZoneOffset.UTC );
+            }
+            if( value instanceof ZonedDateTime )
+            {
+                return ( (ZonedDateTime) value ).toOffsetDateTime().withOffsetSameInstant( ZoneOffset.UTC );
+            }
+        }
+        if( type.equals( ValueType.LOCAL_DATE_TIME ) )
+        {
+            if( value instanceof Timestamp )
+            {
+                return ( (Timestamp) value ).toLocalDateTime();
+            }
+        }
+        if( type.equals( ValueType.PERIOD ) )
+        {
+            if( value instanceof String )
+            {
+                return Period.parse( (String) value );
+            }
+        }
+        if( type.equals( ValueType.DURATION ) )
+        {
+            if( value instanceof String )
+            {
+                return Duration.parse( (String) value );
+            }
+        }
+        if( type.equals( ValueType.LOCAL_DATE ) )
+        {
+            if( value instanceof java.sql.Date )
+            {
+                return ( (java.sql.Date) value ).toLocalDate();
+            }
+        }
+        if( type.equals( ValueType.LOCAL_TIME ) )
+        {
+            if( value instanceof java.sql.Time )
+            {
+                return ( (java.sql.Time) value ).toLocalTime();
+            }
+        }
+        // otherwise, we deal with a serialized value.
+        return serialization.deserialize( module, type, (String) value );
+    }
+
+    private void addNamedAssociation( AssociationStateDescriptor stateDescriptor, Map<QualifiedName, Map<String, EntityReference>> namedAssocs, AssociationValue associationValue )
+    {
+        AssociationDescriptor descriptor = stateDescriptor.getNamedAssociationByName( associationValue.name.name() );
+        QualifiedName qualifiedName = descriptor.qualifiedName();
+        Map<String, EntityReference> map = namedAssocs.computeIfAbsent( qualifiedName, k -> new HashMap<>() );
+        map.put( associationValue.position, parseEntityReference( associationValue.reference ) );
+    }
+
+    private void addManyAssociation( AssociationStateDescriptor stateDescriptor, Map<QualifiedName, List<EntityReference>> manyAssocs, AssociationValue associationValue )
+    {
+        AssociationDescriptor descriptor = stateDescriptor.getManyAssociationByName( associationValue.name.name() );
+        QualifiedName qualifiedName = descriptor.qualifiedName();
+        List<EntityReference> list = manyAssocs.computeIfAbsent( qualifiedName, k -> new ArrayList<>() );
+        String reference = associationValue.reference;
+        list.add( reference == null ? null : parseEntityReference( reference ) );
+    }
+
+    @Override
+    public String versionOf( EntityStoreUnitOfWork unitOfWork, EntityReference reference )
+    {
+        BaseEntity baseEntity = sqlTable.fetchBaseEntity( reference, unitOfWork.module() );
+        return baseEntity.version;
+    }
+
+    @Override
+    public StateCommitter applyChanges( EntityStoreUnitOfWork unitOfWork, Iterable<EntityState> state )
+    {
+        return new JooqStateCommitter( unitOfWork, state, sqlTable.jooqDslContext() );
+    }
+
+    @Override
+    public EntityStoreUnitOfWork newUnitOfWork( ModuleDescriptor module, Usecase usecase, Instant currentTime )
+    {
+        return new DefaultEntityStoreUnitOfWork( module,
+                                                 this,
+                                                 identityGenerator.generate( SqlEntityStoreService.class ),
+                                                 usecase,
+                                                 currentTime
+        );
+    }
+
+    @Override
+    public Stream<EntityState> entityStates( ModuleDescriptor module )
+    {
+        Stream<? extends EntityDescriptor> entityTypes = module.entityComposites();
+        return entityTypes
+            .flatMap( type -> sqlTable.fetchAll( type, module ) )
+            .map( baseEntity ->
+                  {
+                      EntityReference reference = EntityReference.entityReferenceFor( baseEntity.identity );
+                      SelectQuery<Record> selectQuery = sqlTable.createGetEntityQuery( baseEntity.type, reference );
+                      Result<Record> result = selectQuery.fetch();
+                      return toEntityState( result, baseEntity, reference, module );
+                  } );
+    }
+
+    private class JooqStateCommitter
+        implements StateCommitter
+    {
+        private final EntityStoreUnitOfWork unitOfWork;
+        private final Iterable<EntityState> states;
+        private final JooqDslContext dslContext;
+        private final ModuleDescriptor module;
+
+        JooqStateCommitter( EntityStoreUnitOfWork unitOfWork, Iterable<EntityState> states, JooqDslContext dslContext )
+        {
+            this.unitOfWork = unitOfWork;
+            this.states = states;
+            this.dslContext = dslContext;
+            this.module = unitOfWork.module();
+        }
+
+        private void newState( DefaultEntityState state, EntityStoreUnitOfWork unitOfWork )
+        {
+            EntityReference ref = state.entityReference();
+            EntityDescriptor descriptor = state.entityDescriptor();
+            sqlTable.createNewBaseEntity( ref, descriptor, this.unitOfWork );
+            sqlTable.insertEntity( state, sqlTable.fetchBaseEntity( ref, module ), unitOfWork );
+        }
+
+        private void updateState( DefaultEntityState state, EntityStoreUnitOfWork unitOfWork )
+        {
+            EntityDescriptor descriptor = state.entityDescriptor();
+            BaseEntity baseEntity = sqlTable.fetchBaseEntity( state.entityReference(), descriptor.module() );
+            sqlTable.updateEntity( state, baseEntity, unitOfWork );
+        }
+
+        private void removeState( DefaultEntityState state )
+        {
+            EntityReference reference = state.entityReference();
+            EntityDescriptor descriptor = state.entityDescriptor();
+            sqlTable.removeEntity( reference, descriptor );
+        }
+
+        @Override
+        public void commit()
+        {
+            dslContext.transaction( configuration ->
+                                    {
+                                        for( EntityState es : this.states )
+                                        {
+                                            DefaultEntityState state = (DefaultEntityState) es;
+                                            if( state.status() == EntityStatus.NEW )
+                                            {
+                                                newState( state, unitOfWork );
+                                            }
+                                            if( state.status() == EntityStatus.UPDATED )
+                                            {
+                                                updateState( state, unitOfWork );
+                                            }
+                                            if( state.status() == EntityStatus.REMOVED )
+                                            {
+                                                removeState( state );
+                                            }
+                                        }
+                                    } );
+        }
+
+        @Override
+        public void cancel()
+        {
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/6055b8f7/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlEntityStoreService.java
----------------------------------------------------------------------
diff --git a/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlEntityStoreService.java b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlEntityStoreService.java
new file mode 100644
index 0000000..cf401d6
--- /dev/null
+++ b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlEntityStoreService.java
@@ -0,0 +1,36 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you 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 org.apache.polygene.entitystore.sql;
+
+import org.apache.polygene.api.concern.Concerns;
+import org.apache.polygene.api.configuration.Configuration;
+import org.apache.polygene.api.mixin.Mixins;
+import org.apache.polygene.spi.entitystore.ConcurrentModificationCheckConcern;
+import org.apache.polygene.spi.entitystore.EntityStateVersions;
+import org.apache.polygene.spi.entitystore.EntityStore;
+import org.apache.polygene.spi.entitystore.StateChangeNotificationConcern;
+
+/**
+ * SQL EntityStore service.
+ */
+@Concerns( { StateChangeNotificationConcern.class, ConcurrentModificationCheckConcern.class } )
+@Mixins( { SqlEntityStoreMixin.class } )
+public interface SqlEntityStoreService
+    extends EntityStore, EntityStateVersions, Configuration, SqlTable
+{
+}