You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by nt...@apache.org on 2018/04/18 09:59:07 UTC

[03/19] cayenne git commit: Added the selection of the primary key generator outside the adapter

Added the selection of the primary key generator outside the adapter


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/e6088949
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/e6088949
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/e6088949

Branch: refs/heads/master
Commit: e608894905e86ff6e28f4f33c45bccb56aa73b87
Parents: 265636c
Author: Aleksey Pleshkanev <pr...@hotmail.com>
Authored: Sat Mar 10 22:21:48 2018 +0300
Committer: Aleksey Pleshkanev <pr...@hotmail.com>
Committed: Sat Mar 10 22:21:48 2018 +0300

----------------------------------------------------------------------
 .../apache/cayenne/configuration/Constants.java |   7 ++
 .../server/DefaultDbAdapterFactory.java         |   5 +-
 .../server/DefaultPkGeneratorFactory.java       | 118 +++++++++++++++++++
 .../server/PkGeneratorFactory.java              |  22 ++++
 .../configuration/server/ServerModule.java      |  32 ++++-
 .../java/org/apache/cayenne/dba/DbVersion.java  | 108 +++++++++++++++++
 .../dba/sqlserver/SQLServerActionBuilder.java   |   1 +
 .../cayenne/dba/sqlserver/SQLServerAdapter.java |   5 -
 .../cayenne/dba/sqlserver/SQLServerSniffer.java |  36 ++++--
 .../server/DataDomainProviderTest.java          |   9 ++
 .../dba/sqlserver/SQLServerSnifferIT.java       |   6 +-
 .../unit/di/server/ServerCaseModule.java        |  16 ++-
 12 files changed, 338 insertions(+), 27 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/e6088949/cayenne-server/src/main/java/org/apache/cayenne/configuration/Constants.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/configuration/Constants.java b/cayenne-server/src/main/java/org/apache/cayenne/configuration/Constants.java
index b993eea..bc35d84 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/configuration/Constants.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/configuration/Constants.java
@@ -45,6 +45,12 @@ public interface Constants {
     String SERVER_ADAPTER_DETECTORS_LIST = "cayenne.server.adapter_detectors";
 
     /**
+     * A DI container key for the Map&lt;Key, PkGenerator.class&gt; that objects
+     * that can discover the type of current database and install correct PkGenerator in runtime.
+     */
+    String SERVER_PK_GENERATORS_MAP = "cayenne.server.pk_generators";
+
+    /**
      * A DI container key for the List&lt;DataChannelFilter&gt; storing
      * DataDomain filters.
      *
@@ -210,6 +216,7 @@ public interface Constants {
 
     /**
      * Snapshot cache max size
+     *
      * @see org.apache.cayenne.configuration.server.ServerModule#setSnapshotCacheSize(Binder, int)
      * @since 4.0
      */

http://git-wip-us.apache.org/repos/asf/cayenne/blob/e6088949/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/DefaultDbAdapterFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/DefaultDbAdapterFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/DefaultDbAdapterFactory.java
index a95f3a9..f528ea5 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/DefaultDbAdapterFactory.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/DefaultDbAdapterFactory.java
@@ -34,7 +34,6 @@ import org.apache.cayenne.dba.JdbcAdapter;
 import org.apache.cayenne.di.AdhocObjectFactory;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.di.Injector;
-import org.apache.cayenne.di.Provider;
 import org.apache.cayenne.log.JdbcEventLogger;
 
 /**
@@ -53,13 +52,13 @@ public class DefaultDbAdapterFactory implements DbAdapterFactory {
 
 	@Inject
 	protected AdhocObjectFactory objectFactory;
+
 	protected List<DbAdapterDetector> detectors;
 
 	public DefaultDbAdapterFactory(@Inject(Constants.SERVER_ADAPTER_DETECTORS_LIST) List<DbAdapterDetector> detectors) {
 		if (detectors == null) {
 			throw new NullPointerException("Null detectors list");
 		}
-
 		this.detectors = detectors;
 	}
 
@@ -92,7 +91,7 @@ public class DefaultDbAdapterFactory implements DbAdapterFactory {
 			return defaultAdapter();
 		}
 
-		try (Connection c = dataSource.getConnection();) {
+		try (Connection c = dataSource.getConnection()) {
 			return detectAdapter(c.getMetaData());
 		} catch (SQLException e) {
 			throw new CayenneRuntimeException("Error detecting database type: " + e.getLocalizedMessage(), e);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/e6088949/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/DefaultPkGeneratorFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/DefaultPkGeneratorFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/DefaultPkGeneratorFactory.java
new file mode 100644
index 0000000..9a64f99
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/DefaultPkGeneratorFactory.java
@@ -0,0 +1,118 @@
+package org.apache.cayenne.configuration.server;
+
+import org.apache.cayenne.ConfigurationException;
+import org.apache.cayenne.configuration.Constants;
+import org.apache.cayenne.dba.DbVersion;
+import org.apache.cayenne.dba.JdbcAdapter;
+import org.apache.cayenne.dba.JdbcPkGenerator;
+import org.apache.cayenne.dba.PkGenerator;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.log.JdbcEventLogger;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Constructor;
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+import java.util.Map;
+import java.util.Objects;
+
+import static org.apache.cayenne.dba.DbVersion.MS_SQL_2008;
+import static org.apache.cayenne.dba.DbVersion.MS_SQL_2012;
+
+/**
+ * A factory of PkGenerators that also loads user-provided pkGenerator or guesses
+ * the pkGenerator from the database metadata.
+ */
+public class DefaultPkGeneratorFactory implements PkGeneratorFactory {
+
+    @Inject
+    protected JdbcEventLogger jdbcEventLogger;
+
+    protected Map<String, Class> pkGenerators;
+
+    public DefaultPkGeneratorFactory(@Inject(Constants.SERVER_PK_GENERATORS_MAP) Map<String, Class> pkGenerators) {
+        this.pkGenerators = Objects.requireNonNull(pkGenerators, () -> "Null pkGenerators list");
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public PkGenerator detectPkGenerator(DbVersion.DbType dbType, JdbcAdapter adapter, DatabaseMetaData md) throws SQLException {
+
+        final int majorVersion = md.getDatabaseMajorVersion();
+        final int minorVersion = md.getDatabaseMinorVersion();
+
+        Class pkGeneratorClazz = null;
+        DbVersion currentDbVersion = new DbVersion(dbType, majorVersion, minorVersion);
+        switch (dbType) {
+            case MS_SQL:
+                pkGeneratorClazz = detectPkGenerator4MSSQL(currentDbVersion);
+                break;
+            default:
+                jdbcEventLogger.log("Failed to detect PkGenerator, using generic generator");
+                pkGeneratorClazz = defaultPkGenerator();
+        }
+
+        jdbcEventLogger.log("DB - '" + currentDbVersion + "'; PkGenerator - " + pkGeneratorClazz.getName());
+
+        try {
+            return newInstancePk(pkGeneratorClazz, adapter);
+        } catch (Throwable throwable) {
+            throw new ConfigurationException("Could not instantiate " + currentDbVersion, throwable);
+        }
+    }
+
+    /**
+     * @param pkGeneratorClazz {@link Class} for instantiation PkGenerator
+     * @param adapter          adapter for installation in PkGenerator
+     */
+    protected PkGenerator newInstancePk(Class pkGeneratorClazz, JdbcAdapter adapter) throws Throwable {
+        Constructor pkGeneratorConstructor = pkGeneratorClazz.getDeclaredConstructor(JdbcAdapter.class);
+        pkGeneratorConstructor.setAccessible(true);
+
+        MethodHandle createPkGenerator = MethodHandles.lookup()
+                .unreflectConstructor(pkGeneratorConstructor);
+
+        return (PkGenerator) createPkGenerator.invoke(adapter);
+    }
+
+    protected Class defaultPkGenerator() {
+        return JdbcPkGenerator.class;
+    }
+
+    /**
+     * Choosing a specific generator, depending on the version of the database
+     *
+     * @param currentDbVersion version of the database for which you need to determine the PkGenerator
+     */
+    protected Class detectPkGenerator4MSSQL(DbVersion currentDbVersion) {
+
+        if (!isCheckTypeGenerator(currentDbVersion, MS_SQL_2008, MS_SQL_2012)) {
+            return defaultPkGenerator();
+        }
+
+        if (currentDbVersion.compareTo(MS_SQL_2012) >= 0) {
+            String version = String.valueOf(MS_SQL_2012);
+            if (pkGenerators.containsKey(version)) {
+                return pkGenerators.get(version);
+            }
+        } else {
+            String version = String.valueOf(MS_SQL_2008);
+            if (pkGenerators.containsKey(version)) {
+                return pkGenerators.get(version);
+            }
+        }
+
+        jdbcEventLogger.log("Failed to detect PkGenerator, using generic generator");
+        return defaultPkGenerator();
+    }
+
+    private boolean isCheckTypeGenerator(DbVersion currentDbVersion, DbVersion... dbVersions) {
+        for (DbVersion item : dbVersions) {
+            if (!item.isTypeCheck(currentDbVersion)) {
+                return false;
+            }
+        }
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/e6088949/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/PkGeneratorFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/PkGeneratorFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/PkGeneratorFactory.java
new file mode 100644
index 0000000..23429fe
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/PkGeneratorFactory.java
@@ -0,0 +1,22 @@
+package org.apache.cayenne.configuration.server;
+
+import org.apache.cayenne.dba.DbVersion;
+import org.apache.cayenne.dba.JdbcAdapter;
+import org.apache.cayenne.dba.PkGenerator;
+
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+
+public interface PkGeneratorFactory {
+
+    /**
+     * Discovering the primary key based on the database metadata
+     *
+     * @param dbType  database type
+     * @param adapter adapter for generator instantiation
+     * @param md      connection metadata
+     * @return an instantiated instance of a specific generator for the current version of the database
+     * @throws SQLException
+     */
+    PkGenerator detectPkGenerator(DbVersion.DbType dbType, JdbcAdapter adapter, DatabaseMetaData md) throws SQLException;
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/e6088949/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModule.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModule.java b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModule.java
index 8992f44..6a9867d 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModule.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/configuration/server/ServerModule.java
@@ -82,6 +82,7 @@ import org.apache.cayenne.configuration.xml.NoopDataChannelMetaData;
 import org.apache.cayenne.configuration.xml.XMLDataChannelDescriptorLoader;
 import org.apache.cayenne.configuration.xml.XMLDataMapLoader;
 import org.apache.cayenne.configuration.xml.XMLReaderProvider;
+import org.apache.cayenne.dba.PkGenerator;
 import org.apache.cayenne.dba.db2.DB2Sniffer;
 import org.apache.cayenne.dba.derby.DerbySniffer;
 import org.apache.cayenne.dba.firebird.FirebirdSniffer;
@@ -94,7 +95,9 @@ import org.apache.cayenne.dba.openbase.OpenBaseSniffer;
 import org.apache.cayenne.dba.oracle.OracleSniffer;
 import org.apache.cayenne.dba.postgres.PostgresSniffer;
 import org.apache.cayenne.dba.sqlite.SQLiteSniffer;
+import org.apache.cayenne.dba.sqlserver.SQLServerPkGenerator;
 import org.apache.cayenne.dba.sqlserver.SQLServerSniffer;
+import org.apache.cayenne.dba.sybase.SybasePkGenerator;
 import org.apache.cayenne.dba.sybase.SybaseSniffer;
 import org.apache.cayenne.di.AdhocObjectFactory;
 import org.apache.cayenne.di.Binder;
@@ -128,6 +131,9 @@ import org.xml.sax.XMLReader;
 import java.util.Calendar;
 import java.util.GregorianCalendar;
 
+import static org.apache.cayenne.dba.DbVersion.MS_SQL_2008;
+import static org.apache.cayenne.dba.DbVersion.MS_SQL_2012;
+
 /**
  * A DI module containing all Cayenne server runtime configuration.
  *
@@ -140,7 +146,7 @@ public class ServerModule implements Module {
     /**
      * Sets transaction management to either external or internal transactions. Default is internally-managed transactions.
      *
-     * @param binder  DI binder passed to the module during injector startup.
+     * @param binder      DI binder passed to the module during injector startup.
      * @param useExternal whether external (true) or internal (false) transaction management should be used.
      * @since 4.0
      */
@@ -152,7 +158,7 @@ public class ServerModule implements Module {
      * Sets max size of snapshot cache, in pre 4.0 version this was set in the Modeler.
      *
      * @param binder DI binder passed to the module during injector startup.
-     * @param size max size of snapshot cache
+     * @param size   max size of snapshot cache
      * @since 4.0
      */
     public static void setSnapshotCacheSize(Binder binder, int size) {
@@ -207,6 +213,17 @@ public class ServerModule implements Module {
     }
 
     /**
+     * Provides access to a DI map builder for {@link PkGenerator}'s that allows downstream modules to
+     * "contribute" their own pk generators.
+     *
+     * @param binder DI binder passed to the module during injector startup.
+     * @return MapBuilder for properties.
+     */
+    public static MapBuilder<Class> contributePkGenerators(Binder binder) {
+        return binder.bindMap(Class.class, Constants.SERVER_PK_GENERATORS_MAP);
+    }
+
+    /**
      * Provides access to a DI map builder for runtime properties that allows downstream modules to
      * "contribute" their own properties.
      *
@@ -257,7 +274,6 @@ public class ServerModule implements Module {
     }
 
     /**
-     *
      * @param binder DI binder passed to module during injector startup
      * @return ListBuilder for user-contributed ValueObjectTypes
      * @since 4.0
@@ -293,6 +309,10 @@ public class ServerModule implements Module {
                 .add(SQLServerSniffer.class).add(OracleSniffer.class).add(PostgresSniffer.class)
                 .add(MySQLSniffer.class);
 
+        contributePkGenerators(binder)
+                .put(String.valueOf(MS_SQL_2008), SybasePkGenerator.class) //adding a generator for MS SQL version 2012 and higher
+                .put(String.valueOf(MS_SQL_2012), SQLServerPkGenerator.class); //adding a generator since MS SQL version 2012
+
         // configure a filter chain with only one TransactionFilter as default
         contributeDomainFilters(binder).add(TransactionFilter.class);
 
@@ -336,8 +356,8 @@ public class ServerModule implements Module {
 
         binder.bind(DataRowStoreFactory.class).to(DefaultDataRowStoreFactory.class);
 
-		// a service to provide the main stack DataDomain
-		binder.bind(DataDomain.class).toProvider(DataDomainProvider.class);
+        // a service to provide the main stack DataDomain
+        binder.bind(DataDomain.class).toProvider(DataDomainProvider.class);
 
         binder.bind(DataNodeFactory.class).to(DefaultDataNodeFactory.class);
 
@@ -373,6 +393,8 @@ public class ServerModule implements Module {
         // DbAdapters
         binder.bind(DbAdapterFactory.class).to(DefaultDbAdapterFactory.class);
 
+        binder.bind(PkGeneratorFactory.class).to(DefaultPkGeneratorFactory.class);
+
         // binding AshwoodEntitySorter without scope, as this is a stateful
         // object and is
         // configured by the owning domain

http://git-wip-us.apache.org/repos/asf/cayenne/blob/e6088949/cayenne-server/src/main/java/org/apache/cayenne/dba/DbVersion.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/DbVersion.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/DbVersion.java
new file mode 100644
index 0000000..3caca4a
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/DbVersion.java
@@ -0,0 +1,108 @@
+package org.apache.cayenne.dba;
+
+import java.util.Objects;
+
+/**
+ * Description of the database version and its type
+ */
+public class DbVersion implements Comparable<DbVersion> {
+
+    /**
+     * DbVersion: majorVersion = 11, minorVersion = 0
+     */
+    public static final DbVersion MS_SQL_2012;
+
+    /**
+     * DbVersion: majorVersion = 10, minorVersion = 0
+     */
+    public static final DbVersion MS_SQL_2008;
+
+    static {
+        MS_SQL_2012 = new DbVersion(DbType.MS_SQL, 11, 0);
+        MS_SQL_2008 = new DbVersion(DbType.MS_SQL, 10, 0);
+    }
+
+    private final DbType dbType;
+    private final int majorVersion;
+    private final int minorVersion;
+
+    public DbVersion(DbType dbType, int majorVersion, int minorVersion) {
+        this.dbType = dbType;
+        this.majorVersion = majorVersion;
+        this.minorVersion = minorVersion;
+    }
+
+    public int getMajorVersion() {
+        return majorVersion;
+    }
+
+    public int getMinorVersion() {
+        return minorVersion;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (!(obj instanceof DbVersion)) {
+            return false;
+        }
+
+        DbVersion dbVersion = (DbVersion) obj;
+
+        return dbVersion.majorVersion == this.majorVersion &&
+                dbVersion.minorVersion == this.minorVersion;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(this.majorVersion, this.minorVersion);
+    }
+
+    @Override
+    public String toString() {
+        return "DbVersion: [dbType: '" + this.dbType.getType() +
+                "', majorVersion:'" + this.majorVersion +
+                "', minorVersion:'" + this.minorVersion + "']";
+    }
+
+    /**
+     * @return {@code true} if the types of equivalents,
+     * {@code false} otherwise
+     */
+    public boolean isTypeCheck(DbVersion o) {
+        return this.dbType == o.dbType;
+    }
+
+    /**
+     * Compare by version, type is not taken into account.
+     * Before calling this method, do a check:
+     * {@link DbVersion#isTypeCheck(org.apache.cayenne.dba.DbVersion)}
+     */
+    @Override
+    public int compareTo(DbVersion o) {
+        if (o.equals(this)) {
+            return 0;
+        }
+        if (this.majorVersion == o.minorVersion) {
+            return this.minorVersion - o.minorVersion;
+        }
+        return this.majorVersion - o.majorVersion;
+    }
+
+    public enum DbType {
+
+        MS_SQL("MICROSOFT SQL SERVER");
+
+        private final String type;
+
+        DbType(String type) {
+            this.type = type;
+        }
+
+        public String getType() {
+            return type;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/e6088949/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerActionBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerActionBuilder.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerActionBuilder.java
index 6630a8b..530d555 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerActionBuilder.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerActionBuilder.java
@@ -24,6 +24,7 @@ import org.apache.cayenne.dba.JdbcActionBuilder;
 import org.apache.cayenne.query.BatchQuery;
 import org.apache.cayenne.query.ProcedureQuery;
 import org.apache.cayenne.query.SQLAction;
+import org.apache.cayenne.query.SelectQuery;
 
 /**
  * @since 1.2

http://git-wip-us.apache.org/repos/asf/cayenne/blob/e6088949/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java
index d80cc3d..7739ad1 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java
@@ -119,9 +119,4 @@ public class SQLServerAdapter extends SybaseAdapter {
 		translator.setCaseInsensitive(caseInsensitiveCollations);
 		return translator;
 	}
-
-	@Override
-	protected PkGenerator createPkGenerator() {
-		return new SQLServerPkGenerator(this);
-	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/e6088949/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerSniffer.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerSniffer.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerSniffer.java
index c139211..aef4e9c 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerSniffer.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerSniffer.java
@@ -19,31 +19,42 @@
 
 package org.apache.cayenne.dba.sqlserver;
 
-import java.sql.DatabaseMetaData;
-import java.sql.SQLException;
-
 import org.apache.cayenne.configuration.server.DbAdapterDetector;
+import org.apache.cayenne.configuration.server.PkGeneratorFactory;
 import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dba.DbVersion;
+import org.apache.cayenne.dba.PkGenerator;
 import org.apache.cayenne.di.AdhocObjectFactory;
 import org.apache.cayenne.di.Inject;
 
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+
+import static org.apache.cayenne.dba.DbVersion.DbType.MS_SQL;
+
 /**
  * Detects SQLServer database from JDBC metadata.
- * 
+ *
  * @since 1.2
  */
 public class SQLServerSniffer implements DbAdapterDetector {
 
     protected AdhocObjectFactory objectFactory;
 
-    public SQLServerSniffer(@Inject AdhocObjectFactory objectFactory) {
+    protected PkGeneratorFactory pkGeneratorFactory;
+
+    private final DbVersion.DbType dbType;
+
+    public SQLServerSniffer(@Inject AdhocObjectFactory objectFactory, @Inject PkGeneratorFactory pkGeneratorFactory) {
         this.objectFactory = objectFactory;
+        this.pkGeneratorFactory = pkGeneratorFactory;
+        this.dbType = MS_SQL;
     }
 
     @Override
     public DbAdapter createAdapter(DatabaseMetaData md) throws SQLException {
         String dbName = md.getDatabaseProductName();
-        if (dbName == null || !dbName.toUpperCase().contains("MICROSOFT SQL SERVER")) {
+        if (dbName == null || !dbName.toUpperCase().contains(dbType.getType())) {
             return null;
         }
 
@@ -54,16 +65,23 @@ public class SQLServerSniffer implements DbAdapterDetector {
         // detect whether generated keys are supported
 
         boolean generatedKeys = false;
+        PkGenerator pkGenerator = null;
+
         try {
             generatedKeys = md.supportsGetGeneratedKeys();
-        }
-        catch (Throwable th) {
+            if (generatedKeys) {
+                pkGenerator = pkGeneratorFactory.detectPkGenerator(dbType, adapter, md);
+            }
+        } catch (Throwable th) {
             // catch exceptions resulting from incomplete JDBC3 implementation
             // ** we have to catch Throwable, as unimplemented methods would result in
             // "AbstractMethodError".
         }
-
         adapter.setSupportsGeneratedKeys(generatedKeys);
+        if (pkGenerator != null) {
+            adapter.setPkGenerator(pkGenerator);
+        }
+
         return adapter;
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/e6088949/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/DataDomainProviderTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/DataDomainProviderTest.java b/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/DataDomainProviderTest.java
index ad828e0..beba838 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/DataDomainProviderTest.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/configuration/server/DataDomainProviderTest.java
@@ -66,7 +66,9 @@ import org.apache.cayenne.dba.oracle.OracleAdapter;
 import org.apache.cayenne.dba.oracle.OracleSniffer;
 import org.apache.cayenne.dba.postgres.PostgresSniffer;
 import org.apache.cayenne.dba.sqlite.SQLiteSniffer;
+import org.apache.cayenne.dba.sqlserver.SQLServerPkGenerator;
 import org.apache.cayenne.dba.sqlserver.SQLServerSniffer;
+import org.apache.cayenne.dba.sybase.SybasePkGenerator;
 import org.apache.cayenne.dba.sybase.SybaseSniffer;
 import org.apache.cayenne.di.AdhocObjectFactory;
 import org.apache.cayenne.di.ClassLoaderManager;
@@ -95,6 +97,8 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import static org.apache.cayenne.dba.DbVersion.MS_SQL_2008;
+import static org.apache.cayenne.dba.DbVersion.MS_SQL_2012;
 import static org.junit.Assert.*;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -157,6 +161,10 @@ public class DataDomainProviderTest {
             ServerModule.contributeDomainListeners(binder).add(mockListener);
             ServerModule.contributeProjectLocations(binder).add(testConfigName);
 
+            ServerModule.contributePkGenerators(binder)
+                    .put(String.valueOf(MS_SQL_2008), SybasePkGenerator.class)
+                    .put(String.valueOf(MS_SQL_2012), SQLServerPkGenerator.class);
+
             // configure extended types
             ServerModule.contributeDefaultTypes(binder);
             ServerModule.contributeUserTypes(binder);
@@ -186,6 +194,7 @@ public class DataDomainProviderTest {
             binder.bind(DataChannelDescriptorMerger.class).to(DefaultDataChannelDescriptorMerger.class);
             binder.bind(DataChannelDescriptorLoader.class).toInstance(testLoader);
             binder.bind(DbAdapterFactory.class).to(DefaultDbAdapterFactory.class);
+            binder.bind(PkGeneratorFactory.class).to(DefaultPkGeneratorFactory.class);
             binder.bind(RuntimeProperties.class).to(DefaultRuntimeProperties.class);
             binder.bind(BatchTranslatorFactory.class).to(DefaultBatchTranslatorFactory.class);
             binder.bind(SelectTranslatorFactory.class).to(DefaultSelectTranslatorFactory.class);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/e6088949/cayenne-server/src/test/java/org/apache/cayenne/dba/sqlserver/SQLServerSnifferIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/dba/sqlserver/SQLServerSnifferIT.java b/cayenne-server/src/test/java/org/apache/cayenne/dba/sqlserver/SQLServerSnifferIT.java
index d5704a9..ab19411 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/dba/sqlserver/SQLServerSnifferIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/dba/sqlserver/SQLServerSnifferIT.java
@@ -24,6 +24,7 @@ import static org.junit.Assert.assertNull;
 
 import java.sql.Connection;
 
+import org.apache.cayenne.configuration.server.PkGeneratorFactory;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.di.AdhocObjectFactory;
 import org.apache.cayenne.di.Inject;
@@ -47,10 +48,13 @@ public class SQLServerSnifferIT extends ServerCase {
 	@Inject
 	private AdhocObjectFactory objectFactory;
 
+	@Inject
+	private PkGeneratorFactory pkGeneratorFactory;
+
 	@Test
 	public void testCreateAdapter() throws Exception {
 
-		SQLServerSniffer sniffer = new SQLServerSniffer(objectFactory);
+		SQLServerSniffer sniffer = new SQLServerSniffer(objectFactory, pkGeneratorFactory);
 
 		DbAdapter adapter = null;
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/e6088949/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/ServerCaseModule.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/ServerCaseModule.java b/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/ServerCaseModule.java
index df41189..75f4905 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/ServerCaseModule.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/ServerCaseModule.java
@@ -56,9 +56,7 @@ import org.apache.cayenne.configuration.DefaultObjectStoreFactory;
 import org.apache.cayenne.configuration.DefaultRuntimeProperties;
 import org.apache.cayenne.configuration.ObjectStoreFactory;
 import org.apache.cayenne.configuration.RuntimeProperties;
-import org.apache.cayenne.configuration.server.DataSourceFactory;
-import org.apache.cayenne.configuration.server.ServerModule;
-import org.apache.cayenne.configuration.server.ServerRuntime;
+import org.apache.cayenne.configuration.server.*;
 import org.apache.cayenne.configuration.xml.DataChannelMetaData;
 import org.apache.cayenne.configuration.xml.DefaultHandlerFactory;
 import org.apache.cayenne.configuration.xml.HandlerFactory;
@@ -82,7 +80,9 @@ import org.apache.cayenne.dba.oracle.OracleAdapter;
 import org.apache.cayenne.dba.postgres.PostgresAdapter;
 import org.apache.cayenne.dba.sqlite.SQLiteAdapter;
 import org.apache.cayenne.dba.sqlserver.SQLServerAdapter;
+import org.apache.cayenne.dba.sqlserver.SQLServerPkGenerator;
 import org.apache.cayenne.dba.sybase.SybaseAdapter;
+import org.apache.cayenne.dba.sybase.SybasePkGenerator;
 import org.apache.cayenne.di.AdhocObjectFactory;
 import org.apache.cayenne.di.Binder;
 import org.apache.cayenne.di.ClassLoaderManager;
@@ -120,6 +120,9 @@ import org.xml.sax.XMLReader;
 import java.util.Calendar;
 import java.util.GregorianCalendar;
 
+import static org.apache.cayenne.dba.DbVersion.MS_SQL_2008;
+import static org.apache.cayenne.dba.DbVersion.MS_SQL_2012;
+
 public class ServerCaseModule implements Module {
 
     protected DefaultScope testScope;
@@ -156,7 +159,11 @@ public class ServerCaseModule implements Module {
                 // Use soft references instead of default weak.
                 // Should remove problems with random-failing tests (those that are GC-sensitive).
                 .put(Constants.SERVER_OBJECT_RETAIN_STRATEGY_PROPERTY, "soft");
-        
+
+        ServerModule.contributePkGenerators(binder)
+                .put(String.valueOf(MS_SQL_2008), SybasePkGenerator.class)
+                .put(String.valueOf(MS_SQL_2012), SQLServerPkGenerator.class);
+
         // configure extended types
         ServerModule.contributeDefaultTypes(binder)
                 .add(new VoidType())
@@ -200,6 +207,7 @@ public class ServerCaseModule implements Module {
         binder.bind(DbAdapter.class).toProvider(ServerCaseDbAdapterProvider.class);
         binder.bind(JdbcAdapter.class).toProvider(ServerCaseDbAdapterProvider.class);
         binder.bind(UnitDbAdapter.class).toProvider(UnitDbAdapterProvider.class);
+        binder.bind(PkGeneratorFactory.class).to(DefaultPkGeneratorFactory.class);
 
         // this factory is a hack that allows to inject to DbAdapters loaded outside of
         // server runtime... BatchQueryBuilderFactory is hardcoded and whatever is placed