Date   

Re: Migration from Kodo: Any way to make it store foreign keys to later get already loaded objects from cache (for performance)?

dosiwelldi
 

Hi again

I have a plugin for JDOHelper.getObjectIdHack. The aim is to get the primary key as integer/long in JDOQL.


    <extension point="org.datanucleus.store.rdbms.sql_method">
        <sql-method method="JDOHelper.getObjectIdHack" datastore="sqlserver"
            evaluator="xx.yy.datanucleus.GetObjectIdHackRdbms"/>
    </extension>

I worked for queries like 'JDOHelper.getObjectIdHack(ent) == 11'.
But now I found out it did not work for 'JDOHelper.getObjectIdHack(ent) == entId' where entId (int) is a parameter instead of a literal.

I found a solution for my problem, but it is a little ugly. I wanted to ask if I am just stupid or if I really need that workaround? And also is there a better way to wrap the NumericExpression?

The problem is that the mapping that is used in the NumericExpression that is the result of the custom function is applied to the variable entId too (because the code for == compares (and "corrects") the mappings). This will lead to a NPE then in PersistableIdMapping.setObject() because entId has no primary key [String[] pkMemberNames = cmd.getPrimaryKeyMemberNames()] but it got that mapping because of the code for ==.

My ugly fix is to wrap the NumericExpression in another NumericExpression, so only the inner one has PersistableIdMapping and ==-code will not see that.

Since I found no constructor to do that I now abuse the constructor for functions (with a function named " "):
    public NumericExpression(SQLStatement stmt, JavaTypeMapping mapping, String functionName, List args)
   

My initial variant that only worked for 'JDOHelper.getObjectIdHack(ent) == 11':

public class GetObjectIdHackRdbms implements SQLMethod {
    public SQLExpression getExpression(SQLStatement stmt, SQLExpression ignore, List<SQLExpression> args)
        ...
        if (expr.getJavaTypeMapping() instanceof PersistableMapping)
            JavaTypeMapping mapping = new PersistableIdMapping((PersistableMapping)expr.getJavaTypeMapping());
            return new NumericExpression(stmt, expr.getSQLTable(), mapping);
        }
    }
}

Current variant abusing NumericExpression for functions:

public class GetObjectIdHackRdbms implements SQLMethod {
    public SQLExpression getExpression(SQLStatement stmt, SQLExpression ignore, List<SQLExpression> args)
        ...
        if (expr.getJavaTypeMapping() instanceof PersistableMapping)
            JavaTypeMapping mapping = new PersistableIdMapping((PersistableMapping)expr.getJavaTypeMapping());
            JavaTypeMapping map_wrap = stmt.getSQLExpressionFactory().getMappingForType(Integer.class, true);
            NumericExpression tmp = new NumericExpression(stmt, expr.getSQLTable(), mapping);
            List<SQLExpression> tmpArgs = new ArrayList<>(1);
            tmpArgs.add(tmp);
            return new NumericExpression(stmt, map_wrap, " ", tmpArgs);
        }
    }
}

Thanks and kind regards


Re: Breaking change in CLOB columns in PostgreSQL coming from DN 5.2.2

keil@...
 

Hi Andy,

thanks for the clarification and the added note.

Cheers


Re: Breaking change in CLOB columns in PostgreSQL coming from DN 5.2.2

Andy
 

Both are stored as a "TEXT" column in the table.
Postgres doesnt support "CLOB" type in the same way as other RDBMS, so DN simply uses a "TEXT" when "CLOB" specified.
DataNucleus doesn't support non-standard, non-JDBC Postgres "large object" API usage.
The only difference is whether to access the "large string" column using JDBC LONGVARCHAR or the clob accessor. LongVarcharMapping makes more sense (to me), and that also means that if PostgreSQL ever bother to support JDBC CLOB using the JDBC API then it can handle both.
Hence no it is not a "bug" (to me). Now added to migration notes.


Re: Breaking change in CLOB columns in PostgreSQL coming from DN 5.2.2

keil@...
 

Hi Andy,

yes, as in the example project, the column is a String column annotated as CLOB with no further annotation information. With DN 5.2.2 this is stored in a text column using  a ClobColumnMapping and working fine. After the upgrade DN changes to using a LongVarcharColumn, which on itself is also working fine. The problem is with the transition that stops previous data from being readable.

The point with the requirements is perfectly reasonable from my point of view. I think also that both mappings are reasonable for storing String values. The change in the used default mapping was the surprise =). The question basically is: is the change in the default a bug or is it considered the correct default that I think should then be mentioned in the migration guide coming from 5.2.2?

Cheers,

   Christian


Re: Breaking change in CLOB columns in PostgreSQL coming from DN 5.2.2

Andy
 

If you are indeed seeing use of ClobColumnMapping on 5.2.2 and LongVarcharColumnMapping on 5.2.6 then you could add
@Column(jdbcType="CLOB",     extensions=@Extension(vendorName="datanucleus", key="column-mapping-class", value="org.datanucleus.store.rdbms.mapping.column.ClobColumnMapping"))
or
<column jdbc-type="CLOB">    <extension vendor-name="datanucleus" key="column-mapping-class" value="org.datanucleus.store.rdbms.mapping.column.ClobColumnMapping"/></column>

which should override any internally chosen column mapping class.


Re: JPA Multitenancy delete doesn't use tenant-read-ids

stephane
 

As tenant-read-ids is not used for UPDATES, but is used for DELETES I've thought something was forgotten... 

The feature I'm putting in place is to "partition" some data. Then users can access to the data of all the partitions they belong to.

So Multitenancy seems to perfectly fulfill the partitioning and the tenant-read-ids features potentially offer a great flexibility, but, flexibility bring big complexities which I want to evaluate:
- How select,insert,update,delete works on simple objects 
- How do they works on relationship loading
- How L2 cache behave with this feature ?
...


Re: JPA Multitenancy delete doesn't use tenant-read-ids

Andy
 

A DELETE is not a READ. Hence "tenantReadIds" is not for use on a DELETE (or on an INSERT, or an UPDATE). That is the DataNucleus feature definition.
Doesn't mean that is the feature you want.


Re: JPA TestCase for MultiTenancy

Andy
 

Some column is NULL on an SQL statement (INSERT/UPDATE) and shouldn't be. Only you can see the SQL statement issued that causes that SQL exception, and a simple look at the table DDL would reveal why that exception would be thrown


JPA Multitenancy delete doesn't use tenant-read-ids

stephane
 

Hi,

When removing an object, DN only uses TenantId, while it uses the tenant-read-ids when querying.

// Tenants : John,Chris
final
Query query = entityManager.createQuery("select e from Element e ");
final List resultList = query.getResultList();
for (Object o : resultList)
{
entityManager.remove(o);
}
transaction.commit();

SELECT 'net.nature.Element' AS `DN_TYPE`,`E`.`ID`,`E`.`NAME` FROM `ELEMENT` `E` WHERE `E`.`TENANT` IN('John','Chris')
DELETE FROM `ELEMENT` WHERE `ID`=<1614949869149> AND `TENANT`=<'NULL'> ...


As I haven't found in the docs yet how it should work.

The DELETE should be :
1- if Tenant is not part of the identifier and we somewhat strict:
DELETE FROM `ELEMENT` WHERE `ID`=<1614949869149> AND `TENANT`IN('John','Chris') ...
2- if looking at the value of the tenant of this object, we can be very strict
DELETE FROM `ELEMENT` WHERE `ID`=<1614949869149> AND `TENANT`= 'John'
3- if we're a little less strict and tenant is not part of the id (just as it is for updates):
DELETE FROM `ELEMENT` WHERE `ID`=<1614949869149>
Update example:
UPDATE `ELEMENT` SET `NAME`=<'Aluminum 1614949877211 1614950377268'> WHERE `ID`=<1614949869149>

I think DeleteRequest can make use of getTenantReadIds while it only uses getTenantId.
if (multitenancyStatementMapping != null)
{
table.getSurrogateMapping(SurrogateColumnType.MULTITENANCY, false).setObject(ec, ps,
multitenancyStatementMapping.getParameterPositionsForOccurrence(0), ec.getNucleusContext().getMultiTenancyId(ec));
}

Considering the case 3, may mean to remove the code above... 

--
Stephane


Re: Multi Tenancy : find fails with a NPE on DN 2.5.6

stephane
 
Edited

Miss read the doc. TenantProvider need to be an object in the EntityManager setting.

Map map = new HashMap();
map.put(PropertyNames.PROPERTY_MAPPING_TENANT_PROVIDER, new MyTenantProvider());
final EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("APP_UNIT", map);

So far TenantProvider works with JPA, remains to understand why TenantReadIds on the EntityManger doesn't...
--
Stephane


JPA TestCase for MultiTenancy

stephane
 

Hi,
 
As you can see on other topics, I'm facing some difficulties with MultiTenancy in JPA. I haven't diagnose yet why settings are not applied but I translated the JDO TestCase to JPA. 
These tests doesn't run with this error:
java.sql.BatchUpdateException: integrity constraint violation: NOT NULL check constraint; SYS_CT_10117 table: TENANTEDOBJECT column: TENANT
 
Can you have a look ? 

thanks
 

Here is a Patch for JPA general testsuite:


Index: jpa/general/src/java/META-INF/persistence.xml
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/jpa/general/src/java/META-INF/persistence.xml b/jpa/general/src/java/META-INF/persistence.xml
--- a/jpa/general/src/java/META-INF/persistence.xml (revision 77f14e53f198cbf4cf728da2a88cc41188470547)
+++ b/jpa/general/src/java/META-INF/persistence.xml (date 1614931959201)
@@ -139,6 +139,7 @@
         <class>org.datanucleus.samples.typeconversion.ComplicatedType2</class>
         <class>org.datanucleus.samples.annotations.one_many.map_keyclass.MapHolderWithKeyClass</class>
         <class>org.datanucleus.samples.annotations.one_many.map_keyclass.MapKeyClassTarget</class>
+        <class>org.datanucleus.samples.jpa.multitenancy.TenantedObject</class>
     </persistence-unit>
 
 </persistence>
Index: jpa/general/src/java/org/datanucleus/samples/jpa/multitenancy/TenantedObject.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/jpa/general/src/java/org/datanucleus/samples/jpa/multitenancy/TenantedObject.java b/jpa/general/src/java/org/datanucleus/samples/jpa/multitenancy/TenantedObject.java
new file mode 100644
--- /dev/null (date 1614931840553)
+++ b/jpa/general/src/java/org/datanucleus/samples/jpa/multitenancy/TenantedObject.java (date 1614931840553)
@@ -0,0 +1,30 @@
+package org.datanucleus.samples.jpa.multitenancy;
+
+import org.datanucleus.api.jpa.annotations.MultiTenant;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+
+@MultiTenant(column = "TENANT", columnLength = 24)
+public class TenantedObject {
+ @Id
+ long id;
+ String name;
+
+ public long getId() {
+ return this.id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
Index: jpa/general/src/test/org/datanucleus/tests/MultitenancyTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/jpa/general/src/test/org/datanucleus/tests/MultitenancyTest.java b/jpa/general/src/test/org/datanucleus/tests/MultitenancyTest.java
new file mode 100644
--- /dev/null (date 1614934980017)
+++ b/jpa/general/src/test/org/datanucleus/tests/MultitenancyTest.java (date 1614934980017)
@@ -0,0 +1,528 @@
+/**********************************************************************
+ Copyright (c) 2021 Andy Jefferson and others. All rights reserved.
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+
+ 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.
+
+ Contributors:
+ ...
+ **********************************************************************/
+package org.datanucleus.tests;
+
+import org.datanucleus.PropertyNames;
+import org.datanucleus.api.jpa.NucleusJPAHelper;
+import org.datanucleus.samples.jpa.multitenancy.TenantedObject;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.EntityTransaction;
+import javax.validation.ConstraintViolationException;
+import java.util.Properties;
+
+/**
+ * Tests for multitenancy with JDO.
+ */
+public class MultitenancyTest extends JPAPersistenceTestCase {
+    private static boolean initialised = false;
+
+    /**
+     * Constructor.
+     *
+     * @param name Name of the test (not used)
+     */
+    public MultitenancyTest(String name) {
+        super(name);
+        if (!initialised)
+        {
+            addClassesToSchema(new Class[]{TenantedObject.class});
+            initialised = true;
+        }
+    }
+
+    /**
+     * Test basic tenantId specification via persistence property
+     */
+    public void testTenantId() {
+        Properties userProps = new Properties();
+        userProps.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_ID, "MyID");
+        EntityManagerFactory emfTenant1 = getEMF(1, "JPATest", userProps);
+        Properties userProps2 = new Properties();
+        userProps2.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_ID, "MyID2");
+        EntityManagerFactory emfTenant2 = getEMF(1, "JPATest", userProps2);
+        try
+        {
+            // Persist object for first tenant
+            EntityManager em1 = emfTenant1.createEntityManager();
+            EntityTransaction tx1 = em1.getTransaction();
+            Object id1 = null;
+            try
+            {
+                tx1.begin();
+                TenantedObject o1 = new TenantedObject();
+                o1.setId(1);
+                o1.setName("First");
+                em1.persist(o1);
+                tx1.commit();
+                id1 = o1.getId();
+                ;
+            } catch (ConstraintViolationException cve)
+            {
+                // expected
+                LOG.info("Exception correctly thrown : " + cve.getMessage());
+            } finally
+            {
+                if (tx1.isActive())
+                {
+                    tx1.rollback();
+                }
+                em1.close();
+            }
+            // Persist object for second tenant
+            EntityManager em2 = emfTenant2.createEntityManager();
+            EntityTransaction tx2 = em2.getTransaction();
+            Object id2 = null;
+            try
+            {
+                tx2.begin();
+                TenantedObject o2 = new TenantedObject();
+                o2.setId(2);
+                o2.setName("Second");
+                em2.persist(o2);
+                tx2.commit();
+                id2 = o2.getId();
+            } catch (ConstraintViolationException cve)
+            {
+                // expected
+                LOG.info("Exception correctly thrown : " + cve.getMessage());
+            } finally
+            {
+                if (tx2.isActive())
+                {
+                    tx2.rollback();
+                }
+                em2.close();
+            }
+            // Retrieve all objects for first tenant
+            em1 = emfTenant1.createEntityManager();
+            tx1 = em1.getTransaction();
+            try
+            {
+                tx1.begin();
+                // Check basic retrieve of expect object
+                TenantedObject obj1 = (TenantedObject) em1.find(TenantedObject.class, id1);
+                assertNotNull(obj1);
+                TenantedObject obj2 = em1.find(TenantedObject.class, id2);
+                assertNull("Returned object 2 from tenant 1 but shouldn't have", obj2);
+                //                    JDOObjectNotFoundException
+                tx1.commit();
+            } finally
+            {
+                if (tx1.isActive())
+                {
+                    tx1.rollback();
+                }
+                em1.close();
+            }
+            // Retrieve all objects for second tenant
+            em2 = emfTenant2.createEntityManager();
+            tx2 = em2.getTransaction();
+            try
+            {
+                tx2.begin();
+                // Check basic retrieve of expect object
+                TenantedObject obj2 = em2.find(TenantedObject.class, id2);
+                assertNotNull(obj2);
+                TenantedObject obj1 = em2.find(TenantedObject.class, id1);
+                assertNull("Returned object 1 from tenant 2 but shouldn't have", obj1);
+                tx2.commit();
+            } finally
+            {
+                if (tx2.isActive())
+                {
+                    tx2.rollback();
+                }
+                em2.close();
+            }
+        } finally
+        {
+            // Clear data
+            clean(emfTenant1, TenantedObject.class);
+            clean(emfTenant2, TenantedObject.class);
+            // Close PMFs
+            emfTenant1.close();
+            emfTenant2.close();
+        }
+    }
+    /**
+     * Test tenantId specification via MultitenancyProvider
+     */
+    // TODO
+
+    /**
+     * Test tenantReadIds specification via persistence property
+     */
+    public void testTenantIdWithReadIds() {
+        Properties userProps = new Properties();
+        userProps.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_ID, "MyID");
+        userProps.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_READ_IDS, "MyID,MyID2");
+        EntityManagerFactory pmfTenant1 = getEMF(1, "JPATest", userProps);
+        Properties userProps2 = new Properties();
+        userProps2.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_ID, "MyID2");
+        userProps2.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_READ_IDS, "MyID,MyID2");
+        EntityManagerFactory pmfTenant2 = getEMF(1, "JPATest", userProps2);
+        try
+        {
+            // Persist object for first tenant
+            EntityManager em1 = pmfTenant1.createEntityManager();
+            EntityTransaction tx1 = em1.getTransaction();
+            Object id1 = null;
+            try
+            {
+                tx1.begin();
+                TenantedObject o1 = new TenantedObject();
+                o1.setId(1);
+                o1.setName("First");
+                em1.persist(o1);
+                tx1.commit();
+                id1 = o1.getId();
+                ;
+            } catch (ConstraintViolationException cve)
+            {
+                // expected
+                LOG.info("Exception correctly thrown : " + cve.getMessage());
+            } finally
+            {
+                if (tx1.isActive())
+                {
+                    tx1.rollback();
+                }
+                em1.close();
+            }
+            // Persist object for second tenant
+            EntityManager em2 = pmfTenant2.createEntityManager();
+            EntityTransaction tx2 = em2.getTransaction();
+            Object id2 = null;
+            try
+            {
+                tx2.begin();
+                TenantedObject o2 = new TenantedObject();
+                o2.setId(2);
+                o2.setName("Second");
+                em2.persist(o2);
+                tx2.commit();
+                id2 = o2.getId();
+                ;
+            } catch (ConstraintViolationException cve)
+            {
+                // expected
+                LOG.info("Exception correctly thrown : " + cve.getMessage());
+            } finally
+            {
+                if (tx2.isActive())
+                {
+                    tx2.rollback();
+                }
+                em2.close();
+            }
+            // Retrieve all objects for first tenant
+            em1 = pmfTenant1.createEntityManager();
+            tx1 = em1.getTransaction();
+            try
+            {
+                tx1.begin();
+                // Check basic retrieve of expect objects
+                TenantedObject obj1 = em1.find(TenantedObject.class,id1);
+                assertNotNull(obj1);
+                TenantedObject obj2 =  em1.find(TenantedObject.class,id2);
+                assertNotNull(obj2);
+                tx1.commit();
+            } finally
+            {
+                if (tx1.isActive())
+                {
+                    tx1.rollback();
+                }
+                em1.close();
+            }
+            // Retrieve all objects for second tenant
+            em2 = pmfTenant2.createEntityManager();
+            tx2 = em2.getTransaction();
+            try
+            {
+                tx2.begin();
+                // Check basic retrieve of expect objects
+                TenantedObject obj1 = em2.find(TenantedObject.class,id1);
+                assertNotNull(obj1);
+                TenantedObject obj2 = em2.find(TenantedObject.class,id2);
+                assertNotNull(obj2);
+                tx2.commit();
+            } finally
+            {
+                if (tx2.isActive())
+                {
+                    tx2.rollback();
+                }
+                em2.close();
+            }
+        } finally
+        {
+            // Clear data
+            clean(pmfTenant1, TenantedObject.class);
+            clean(pmfTenant2, TenantedObject.class);
+            // Close PMFs
+            pmfTenant1.close();
+            pmfTenant2.close();
+        }
+    }
+    /**
+     * Test tenantReadIds via EntityManager
+     */
+    public void testTenantIdViaEntityManager() {
+        Properties userProps = new Properties();
+//        userProps.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_ID, "MyID");
+        EntityManagerFactory emfTenant1 = getEMF(1, "JPATest", userProps);
+        Properties userProps2 = new Properties();
+//        userProps2.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_ID, "MyID2");
+        EntityManagerFactory emfTenant2 = getEMF(1, "JPATest", userProps2);
+        try
+        {
+            // Persist object for first tenant
+            EntityManager em1 = emfTenant1.createEntityManager();
+            em1.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_ID, "MyID");
+            EntityTransaction tx1 = em1.getTransaction();
+            Object id1 = null;
+            try
+            {
+                tx1.begin();
+                TenantedObject o1 = new TenantedObject();
+                o1.setId(1);
+                o1.setName("First");
+                em1.persist(o1);
+                tx1.commit();
+                id1 = o1.getId();
+                ;
+            } catch (ConstraintViolationException cve)
+            {
+                // expected
+                LOG.info("Exception correctly thrown : " + cve.getMessage());
+            } finally
+            {
+                if (tx1.isActive())
+                {
+                    tx1.rollback();
+                }
+                em1.close();
+            }
+            // Persist object for second tenant
+            EntityManager em2 = emfTenant2.createEntityManager();
+            em2.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_ID, "MyID2");
+            EntityTransaction tx2 = em2.getTransaction();
+            Object id2 = null;
+            try
+            {
+                tx2.begin();
+                TenantedObject o2 = new TenantedObject();
+                o2.setId(2);
+                o2.setName("Second");
+                em2.persist(o2);
+                tx2.commit();
+                id2 = o2.getId();
+            } catch (ConstraintViolationException cve)
+            {
+                // expected
+                LOG.info("Exception correctly thrown : " + cve.getMessage());
+            } finally
+            {
+                if (tx2.isActive())
+                {
+                    tx2.rollback();
+                }
+                em2.close();
+            }
+            // Retrieve all objects for first tenant
+            em1 = emfTenant1.createEntityManager();
+            em1.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_ID, "MyID");
+            tx1 = em1.getTransaction();
+            try
+            {
+                tx1.begin();
+                // Check basic retrieve of expect object
+                TenantedObject obj1 = (TenantedObject) em1.find(TenantedObject.class, id1);
+                assertNotNull(obj1);
+                TenantedObject obj2 = em1.find(TenantedObject.class, id2);
+                assertNull("Returned object 2 from tenant 1 but shouldn't have", obj2);
+                //                    JDOObjectNotFoundException
+                tx1.commit();
+            } finally
+            {
+                if (tx1.isActive())
+                {
+                    tx1.rollback();
+                }
+                em1.close();
+            }
+            // Retrieve all objects for second tenant
+            em2 = emfTenant2.createEntityManager();
+            em2.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_ID, "MyID2");
+            tx2 = em2.getTransaction();
+            try
+            {
+                tx2.begin();
+                // Check basic retrieve of expect object
+                TenantedObject obj2 = em2.find(TenantedObject.class, id2);
+                assertNotNull(obj2);
+                TenantedObject obj1 = em2.find(TenantedObject.class, id1);
+                assertNull("Returned object 1 from tenant 2 but shouldn't have", obj1);
+                tx2.commit();
+            } finally
+            {
+                if (tx2.isActive())
+                {
+                    tx2.rollback();
+                }
+                em2.close();
+            }
+        } finally
+        {
+            // Clear data
+            clean(emfTenant1, TenantedObject.class);
+            clean(emfTenant2, TenantedObject.class);
+            // Close PMFs
+            emfTenant1.close();
+            emfTenant2.close();
+        }
+    }
+    public void testTenantIdWithReadIdsViaEntityManager() {
+        Properties userProps = new Properties();
+//        userProps.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_ID, "MyID");
+//        userProps.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_READ_IDS, "MyID,MyID2");
+        EntityManagerFactory pmfTenant1 = getEMF(1, "JPATest", userProps);
+        Properties userProps2 = new Properties();
+//        userProps2.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_ID, "MyID2");
+//        userProps2.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_READ_IDS, "MyID,MyID2");
+        EntityManagerFactory pmfTenant2 = getEMF(1, "JPATest", userProps2);
+        try
+        {
+            // Persist object for first tenant
+            EntityManager em1 = pmfTenant1.createEntityManager();
+                    em1.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_ID, "MyID");
+                   em1.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_READ_IDS, "MyID,MyID2");
+            EntityTransaction tx1 = em1.getTransaction();
+            Object id1 = null;
+            try
+            {
+                tx1.begin();
+                TenantedObject o1 = new TenantedObject();
+                o1.setId(1);
+                o1.setName("First");
+                em1.persist(o1);
+                tx1.commit();
+                id1 = o1.getId();
+                ;
+            } catch (ConstraintViolationException cve)
+            {
+                // expected
+                LOG.info("Exception correctly thrown : " + cve.getMessage());
+            } finally
+            {
+                if (tx1.isActive())
+                {
+                    tx1.rollback();
+                }
+                em1.close();
+            }
+            // Persist object for second tenant
+            EntityManager em2 = pmfTenant2.createEntityManager();
+            em2.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_ID, "MyID2");
+            em2.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_READ_IDS, "MyID,MyID2");
+            EntityTransaction tx2 = em2.getTransaction();
+            Object id2 = null;
+            try
+            {
+                tx2.begin();
+                TenantedObject o2 = new TenantedObject();
+                o2.setId(2);
+                o2.setName("Second");
+                em2.persist(o2);
+                tx2.commit();
+                id2 = o2.getId();
+                ;
+            } catch (ConstraintViolationException cve)
+            {
+                // expected
+                LOG.info("Exception correctly thrown : " + cve.getMessage());
+            } finally
+            {
+                if (tx2.isActive())
+                {
+                    tx2.rollback();
+                }
+                em2.close();
+            }
+            // Retrieve all objects for first tenant
+            em1 = pmfTenant1.createEntityManager();
+            em1.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_ID, "MyID");
+            tx1 = em1.getTransaction();
+            try
+            {
+                tx1.begin();
+                // Check basic retrieve of expect objects
+                TenantedObject obj1 = em1.find(TenantedObject.class,id1);
+                assertNotNull(obj1);
+                TenantedObject obj2 =  em1.find(TenantedObject.class,id2);
+                assertNotNull(obj2);
+                tx1.commit();
+            } finally
+            {
+                if (tx1.isActive())
+                {
+                    tx1.rollback();
+                }
+                em1.close();
+            }
+            // Retrieve all objects for second tenant
+            em2 = pmfTenant2.createEntityManager();
+            em2.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_ID, "MyID2");
+            em2.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_READ_IDS, "MyID,MyID2");
+            tx2 = em2.getTransaction();
+            try
+            {
+                tx2.begin();
+                // Check basic retrieve of expect objects
+                TenantedObject obj1 = em2.find(TenantedObject.class,id1);
+                assertNotNull(obj1);
+                TenantedObject obj2 = em2.find(TenantedObject.class,id2);
+                assertNotNull(obj2);
+                tx2.commit();
+            } finally
+            {
+                if (tx2.isActive())
+                {
+                    tx2.rollback();
+                }
+                em2.close();
+            }
+        } finally
+        {
+            // Clear data
+            clean(pmfTenant1, TenantedObject.class);
+            clean(pmfTenant2, TenantedObject.class);
+            // Close PMFs
+            pmfTenant1.close();
+            pmfTenant2.close();
+        }
+    }
+    /**
+     * Test tenantReadIds specification via MultitenancyProvider
+     */
+    // TODO
+
+}
\ No newline at end of file
 
 
 
--
Stephane


Re: Error when using the jdbcType NVARCHAR with H2

mwhesse@...
 

Ok, thanks Andy!

Will change the local type as suggested.


Re: Error when using the jdbcType NVARCHAR with H2

Andy
 
Edited

"Actually JDBC metadata is not a reliable source of information in all or almost all drivers, don't trust it."

Says much, about the attitude in the "database community" to a standard that was intended to limit the amount of hardcoding of database specific nonsense in downstream software ("some other JDBC driver churns out untrustworthy results from this so we don't have to"). As do his later comments regarding "good frameworks" (attempt to distract attention from the output from JDBC and whether JDBC provides what is required to express his databases support) ... aka unconstructive. Needless to say I won't be having anything to do with such crap.


If they simply treat NVARCHAR as VARCHAR and seemingly have no plan of ever supporting it, then you should use VARCHAR.


Re: Multi Tenancy : find fails with a NPE on DN 2.5.6

stephane
 

Using properties.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_PROVIDER,MyTenantProvider.class.getName()) raises an error

WARN   Error accessing property datanucleus.TenantProvider; should be an instance of MultiTenancyProvider but isnt! Ignored
--
Stephane


Re: Error when using the jdbcType NVARCHAR with H2

mwhesse@...
 

Interesting outcome in the discussion with Evgenij, H2 accepts NVARCHAR but doesn't directly support it. You can use it in H2 DDL though, will just be converted to VARCHAR.

Wondering if it would be better to use "VARCHAR" as local type name in our custom JDBC type above?


Re: Error when using the jdbcType NVARCHAR with H2

mwhesse@...
 

Thanks for your support, below works for us.

SQLTypeInfo sqlType = new H2TypeInfo(
        "NVARCHAR", // String typeName
        (short)Types.NVARCHAR, // short dataType
        2147483647, // int precision
        "'", // String literalPrefix
        "'", // String literalSuffix
        "LENGTH", // String createParams
        1, // int nullable
        true, // boolean caseSensitive
        (short)3, // short searchable 
        false, // boolean unsignedAttribute
        false, // boolean fixedPrecScale
        false, // boolean autoIncrement
        "NVARCHAR", // String localTypeName
        (short)0, // short minimumScale
        (short)0, // short maximumScale
        0 // int numPrecRadix
);
addSQLTypeForJDBCType(handler, mconn, (short)Types.NVARCHAR, sqlType, true);    


Re: Error when using the jdbcType NVARCHAR with H2

mwhesse@...
 

A committer on the H2 project suggested that "Actually JDBC metadata is not a reliable source of information in all or almost all drivers, don't trust it.". [1]

Maybe the person is right and we should just do what we think H2 can do, not what H2 actually tells us? In the case of H2 that would mean to not ask the driver I guess?

[1] https://github.com/h2database/h2database/issues/3050


Re: Multi Tenancy : find fails with a NPE on DN 2.5.6

stephane
 

Unable to set the multitenant properties (on current source):

props.put(PropertyNames.PROPERTY_MAPPING_TENANT_READ_IDS, tenant);
props.put(PropertyNames.PROPERTY_MAPPING_TENANT_ID, tenant);
final EntityManager entityManager = emf.createEntityManager(props);
Has no effect.

entityManager
.setProperty(PropertyNames.PROPERTY_MAPPING_TENANT_READ_IDS, tenant);
Raise this log
WARN   Attempt to set property "datanucleus.TenantReadIds" on PM/EM yet this is not supported. Ignored

I tried to set these properties in the EMF properties without more success.


Any suggestions ?

--
Stephane


Re: Error when using the jdbcType NVARCHAR with H2

mwhesse@...
 

Yes H2 does indeed claim support for NVARCHAR:

https://h2database.com/html/datatypes.html?highlight=NVARCHAR&search=NVARCHAR#varchar_type

But it fails to expose it via it's JDBC driver as supported JDBC Type.

Ok thanks on the hint with the custom adapter, we will try that approach and if it works we will submit a PR.


Re: Error when using the jdbcType NVARCHAR with H2

Andy
 

So does H2 actually claim to support "NVARCHAR" ? By that I mean in its response to DatabaseMetadata.getTypeInfo()?
You can easily run DataNucleus SchemaTool in "dbinfo" mode, and get output like the examples in this folder.

If it doesn't actually list it, but would allow a table to be defined using it, then it would be possible to artificially add it to the DataNucleus H2 adapter (see here for how we do it for UUID and GEOMETRY types). If it doesn't return it itself you could get the code and contribute something like that perhaps, via a Pull Request

61 - 80 of 365