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:02 UTC

[06/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-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlTable.java
----------------------------------------------------------------------
diff --git a/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlTable.java b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlTable.java
new file mode 100644
index 0000000..6537782
--- /dev/null
+++ b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlTable.java
@@ -0,0 +1,271 @@
+/*
+ *  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.util.function.Consumer;
+import java.util.stream.Stream;
+import javax.sql.DataSource;
+import org.apache.polygene.api.PolygeneAPI;
+import org.apache.polygene.api.composite.TransientBuilderFactory;
+import org.apache.polygene.api.configuration.Configuration;
+import org.apache.polygene.api.entity.EntityDescriptor;
+import org.apache.polygene.api.entity.EntityReference;
+import org.apache.polygene.api.injection.scope.Service;
+import org.apache.polygene.api.injection.scope.Structure;
+import org.apache.polygene.api.injection.scope.This;
+import org.apache.polygene.api.injection.scope.Uses;
+import org.apache.polygene.api.mixin.Mixins;
+import org.apache.polygene.api.object.ObjectFactory;
+import org.apache.polygene.api.service.ServiceActivation;
+import org.apache.polygene.api.service.ServiceDescriptor;
+import org.apache.polygene.api.structure.Application;
+import org.apache.polygene.api.structure.ModuleDescriptor;
+import org.apache.polygene.spi.entitystore.EntityStoreUnitOfWork;
+import org.apache.polygene.spi.entitystore.helpers.DefaultEntityState;
+import org.jooq.Record;
+import org.jooq.SQLDialect;
+import org.jooq.Schema;
+import org.jooq.SelectQuery;
+import org.jooq.conf.RenderNameStyle;
+import org.jooq.conf.Settings;
+import org.jooq.impl.DSL;
+
+/**
+ * This class handles all the Jooq interactions.
+ * <p>
+ * <p>
+ * <p>
+ * <h1>Tables</h1>
+ * <h2>Types Table</h2>
+ * <ul>
+ * <li>identity</li>
+ * <li>table_name</li>
+ * <li>created_at</li>
+ * <li>modified_at</li>
+ * </ul>
+ * <h2>Entities Table</h2>
+ * <ul>
+ * <li>identity</li>
+ * <li>app_version</li>
+ * <li>value_id</li>
+ * <li>version</li>
+ * <li>type</li>
+ * <li>modified_at</li>
+ * <li>created_at</li>
+ * </ul>
+ * <h2>Mixin Tables</h2>
+ * <p>
+ * Each Mixin is stored in its own table. Only the following column is always present;
+ * <ul>
+ * <li>identity - this is not entity identity but the UUID of the value_id in the Entities Table above.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * Each Property of the Mixin (as defined by QualifiedName of the Property, will reside in its own column.
+ * All values in columns are (for now) serialized using a ValueSerialization service.
+ * </p>
+ * <p>
+ * Associations also has their own columns in the table, with the EntityReference.identity() stored in them.
+ * </p>
+ * <p>
+ * ManyAssociations and NamedAssociations are stored in a separate table, named &lt;mixintable&gt;_ASSOCS, see below.
+ * </p>
+ * <h2>Mixin_ASSOCS Table</h2>
+ * <ul>
+ * <li>identity - the value_id of the mixin value</li>
+ * <li>name - the name of the ManyAssociation or NamedAssociation</li>
+ * <li>position - for NamedAssociation this is the 'name' (i.e key) and for ManyAssociation this is the index into the list.</li>
+ * <li>reference - EntityReference.identity of that association</li>
+ * </ul>
+ */
+@Mixins( SqlTable.Mixin.class )
+public interface SqlTable extends ServiceActivation
+{
+    BaseEntity fetchBaseEntity( EntityReference reference, ModuleDescriptor module );
+
+    SelectQuery<Record> createGetEntityQuery( EntityDescriptor descriptor, EntityReference reference );
+
+    void fetchAssociations( BaseEntity entity, EntityDescriptor descriptor, Consumer<AssociationValue> consume );
+
+    void createNewBaseEntity( EntityReference ref, EntityDescriptor descriptor, EntityStoreUnitOfWork unitOfWork );
+
+    void insertEntity( DefaultEntityState state, BaseEntity baseEntity, EntityStoreUnitOfWork unitOfWork );
+
+    void updateEntity( DefaultEntityState state, BaseEntity baseEntity, EntityStoreUnitOfWork unitOfWork );
+
+    JooqDslContext jooqDslContext();
+
+    void removeEntity( EntityReference entityReference, EntityDescriptor descriptor );
+
+    Stream<BaseEntity> fetchAll( EntityDescriptor type, ModuleDescriptor module );
+
+    class Mixin
+        implements SqlTable, TableFields, ServiceActivation
+    {
+        @Structure
+        private Application application;
+
+        @Structure
+        private TransientBuilderFactory tbf;
+
+        @This
+        private Configuration<SqlEntityStoreConfiguration> configuration;
+
+        @Service
+        private DataSource datasource;
+
+        @Uses
+        private ServiceDescriptor serviceDescriptor;
+
+        private EntitiesTable entitiesTable;
+
+        private TypesTable types;
+        private JooqDslContext dsl;
+
+        @Override
+        public BaseEntity fetchBaseEntity( EntityReference reference, ModuleDescriptor module )
+        {
+            return entitiesTable.fetchEntity( reference, module );
+        }
+
+        @Override
+        public Stream<BaseEntity> fetchAll( EntityDescriptor type, ModuleDescriptor module )
+        {
+            return entitiesTable.fetchAll( type, module );
+        }
+
+        @Override
+        public SelectQuery<Record> createGetEntityQuery( EntityDescriptor descriptor, EntityReference reference )
+        {
+            return entitiesTable.createGetEntityQuery( descriptor, reference );
+        }
+
+        @Override
+        public void fetchAssociations( BaseEntity entity, EntityDescriptor descriptor, Consumer<AssociationValue> consume )
+        {
+            entitiesTable.fetchAssociations( entity, descriptor, consume );
+        }
+
+        @Override
+        public void createNewBaseEntity( EntityReference ref, EntityDescriptor descriptor, EntityStoreUnitOfWork unitOfWork )
+        {
+            entitiesTable.createNewBaseEntity( ref, descriptor, unitOfWork );
+        }
+
+        @Override
+        public void insertEntity( DefaultEntityState state, BaseEntity baseEntity, EntityStoreUnitOfWork unitOfWork )
+        {
+            entitiesTable.insertEntity( state, baseEntity );
+        }
+
+        @Override
+        public void updateEntity( DefaultEntityState state, BaseEntity baseEntity, EntityStoreUnitOfWork unitOfWork )
+        {
+            entitiesTable.modifyEntity( state, baseEntity, unitOfWork );
+        }
+
+        @Override
+        public JooqDslContext jooqDslContext()
+        {
+            return dsl;
+        }
+
+        @Override
+        public void removeEntity( EntityReference reference, EntityDescriptor descriptor )
+        {
+            entitiesTable.removeEntity( reference, descriptor );
+        }
+
+        @Override
+        public void activateService()
+            throws Exception
+        {
+            SqlEntityStoreConfiguration config = this.configuration.get();
+            SQLDialect dialect = getSqlDialect( config );
+
+            Settings settings = serviceDescriptor
+                .metaInfo( Settings.class )
+                .withRenderNameStyle( RenderNameStyle.QUOTED );
+            dsl = tbf.newTransient( JooqDslContext.class, settings, dialect );
+
+            String schemaName = config.schemaName().get();
+            String typesTableName = config.typesTableName().get();
+            String entitiesTableName = config.entitiesTableName().get();
+            Schema schema = DSL.schema( DSL.name( schemaName ) );
+            types = new TypesTable( dsl, schema, dialect, typesTableName );
+            entitiesTable = new EntitiesTable( dsl, schema, types, application.version(), entitiesTableName );
+
+            // Eventually create schema
+            if( config.createIfMissing().get() )
+            {
+                if( !dialect.equals( SQLDialect.SQLITE )
+                    && dsl.meta().getSchemas().stream().noneMatch( s -> schema.getName().equalsIgnoreCase( s.getName() ) ) )
+                {
+                    dsl.createSchema( schema ).execute();
+                }
+
+                dsl.createTableIfNotExists( DSL.name( schemaName, typesTableName ) )
+                   .column( identityColumn )
+                   .column( tableNameColumn )
+                   .column( createdColumn )
+                   .column( modifiedColumn )
+                   .execute();
+
+                dsl.createTableIfNotExists( DSL.name( schemaName, entitiesTableName ) )
+                   .column( identityColumn )
+                   .column( applicationVersionColumn )
+                   .column( valueIdentityColumn )
+                   .column( versionColumn )
+                   .column( typeNameColumn )
+                   .column( modifiedColumn )
+                   .column( createdColumn )
+                   .execute();
+            }
+            datasource.getConnection().commit();
+        }
+
+        @Override
+        public void passivateService()
+            throws Exception
+        {
+
+        }
+
+        private SQLDialect getSqlDialect( SqlEntityStoreConfiguration config )
+        {
+            SQLDialect dialect = null;
+            String dialectString = config.dialect().get();
+            if( dialectString.length() == 0 )
+            {
+                dialect = SQLDialect.DEFAULT;
+            }
+            else
+            {
+                try
+                {
+                    dialect = SQLDialect.valueOf( dialectString );
+                }
+                catch( IllegalArgumentException e )
+                {
+                    throw new IllegalArgumentException( "Invalid SQLDialect: '" + dialectString + "'" );
+                }
+            }
+            return dialect;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/6055b8f7/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlType.java
----------------------------------------------------------------------
diff --git a/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlType.java b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlType.java
new file mode 100644
index 0000000..21de44d
--- /dev/null
+++ b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/SqlType.java
@@ -0,0 +1,156 @@
+/*
+ *  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.math.BigDecimal;
+import java.math.BigInteger;
+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.DefaultDataType;
+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.REAL;
+        }
+        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( Byte.class.isAssignableFrom( propertyType ) )
+        {
+            return (DataType<T>) SQLDataType.INTEGER;
+        }
+        if( BigDecimal.class.isAssignableFrom( propertyType ) )
+        {
+            return (DataType<T>) SQLDataType.DECIMAL;
+        }
+        if( BigInteger.class.isAssignableFrom( propertyType ) )
+        {
+            return (DataType<T>) SQLDataType.DECIMAL(50, 0);
+        }
+        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.REAL;
+            }
+            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-sql/src/main/java/org/apache/polygene/entitystore/sql/TableFields.java
----------------------------------------------------------------------
diff --git a/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/TableFields.java b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/TableFields.java
new file mode 100644
index 0000000..cfa9d99
--- /dev/null
+++ b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/TableFields.java
@@ -0,0 +1,69 @@
+/*
+ *  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 org.jooq.Field;
+
+import static org.apache.polygene.entitystore.sql.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-sql/src/main/java/org/apache/polygene/entitystore/sql/TypesTable.java
----------------------------------------------------------------------
diff --git a/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/TypesTable.java b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/TypesTable.java
new file mode 100644
index 0000000..d04bc95
--- /dev/null
+++ b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/TypesTable.java
@@ -0,0 +1,189 @@
+/*
+ *  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.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-sql/src/main/java/org/apache/polygene/entitystore/sql/assembly/SqlEntityStoreAssembler.java
----------------------------------------------------------------------
diff --git a/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/assembly/SqlEntityStoreAssembler.java b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/assembly/SqlEntityStoreAssembler.java
new file mode 100644
index 0000000..b1f660f
--- /dev/null
+++ b/extensions/entitystore-sql/src/main/java/org/apache/polygene/entitystore/sql/assembly/SqlEntityStoreAssembler.java
@@ -0,0 +1,77 @@
+/*
+ *  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.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.sql.JooqDslContext;
+import org.apache.polygene.entitystore.sql.SqlEntityStoreConfiguration;
+import org.apache.polygene.entitystore.sql.SqlEntityStoreService;
+import org.jooq.SQLDialect;
+import org.jooq.conf.RenderNameStyle;
+import org.jooq.conf.Settings;
+
+/**
+ * JOOQ EntityStore assembly.
+ */
+@SuppressWarnings( "WeakerAccess" )
+public class SqlEntityStoreAssembler extends Assemblers.VisibilityIdentityConfig<SqlEntityStoreAssembler>
+    implements Assembler
+{
+    public static final Identity DEFAULT_ENTITYSTORE_IDENTITY = StringIdentity.identityOf( "entitystore-sql" );
+
+    @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( SqlEntityStoreService.class )
+              .identifiedBy( identity )
+              .visibleIn( visibility() )
+              .instantiateOnStartup()
+              .setMetaInfo( settings );
+
+        if( hasConfig() )
+        {
+            configModule().entities( SqlEntityStoreConfiguration.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-sql/src/test/java/org/apache/polygene/entitystore/sql/SqlEntityStoreTest.java
----------------------------------------------------------------------
diff --git a/extensions/entitystore-sql/src/test/java/org/apache/polygene/entitystore/sql/SqlEntityStoreTest.java b/extensions/entitystore-sql/src/test/java/org/apache/polygene/entitystore/sql/SqlEntityStoreTest.java
new file mode 100644
index 0000000..d8f0d59
--- /dev/null
+++ b/extensions/entitystore-sql/src/test/java/org/apache/polygene/entitystore/sql/SqlEntityStoreTest.java
@@ -0,0 +1,88 @@
+/*
+ *  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.Visibility;
+import org.apache.polygene.bootstrap.AssemblyException;
+import org.apache.polygene.bootstrap.ModuleAssembly;
+import org.apache.polygene.entitystore.sql.assembly.SqlEntityStoreAssembler;
+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 SqlEntityStoreTest 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 SqlEntityStoreAssembler()
+            .withConfig( config, Visibility.layer )
+            .identifiedBy( "sql-entitystore" )
+            .assemble( module );
+        // END SNIPPET: assembly
+
+        SqlEntityStoreConfiguration jooqDefaults = config.forMixin( SqlEntityStoreConfiguration.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/settings.gradle
----------------------------------------------------------------------
diff --git a/settings.gradle b/settings.gradle
index 78570dc..3a4067c 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -61,13 +61,13 @@ include 'core:api',
         'extensions:entitystore-hazelcast',
         'extensions:entitystore-jclouds',
         'extensions:entitystore-jdbm',
-        'extensions:entitystore-jooq',
         'extensions:entitystore-leveldb',
         'extensions:entitystore-memory',
         'extensions:entitystore-mongodb',
         'extensions:entitystore-preferences',
         'extensions:entitystore-redis',
         'extensions:entitystore-riak',
+        'extensions:entitystore-sql',
         'extensions:entitystore-sqlkv',
         'extensions:indexing-elasticsearch',
         'extensions:indexing-rdf',