PostgreSQL Adapter - Potential bug with DELETE operations


#1

Hey guys,

I’m trying to get the PostgreSQL adapter working between a small table I have in PostgreSQL and a small Realm I have set up. Insert/Update works just fine, but when I delete I get “Error: Invalid null value for non-nullable primary key”.
For reference, my PostgreSQL table is set up like so:

CREATE TABLE public.branch (
    branch_code character varying(5) NOT NULL,
    branch_name character varying(30)
);
ALTER TABLE public.branch OWNER TO postgres;
ALTER TABLE ONLY public.branch
    ADD CONSTRAINT branch_pkey PRIMARY KEY (branch_code);

And my Realm model/schema like this:

  const Branch = {
    name: 'Branch',
    primaryKey: 'branchCode',
    properties: {
      branchCode: 'string',
      branchName: {type: 'string', optional: true}
    }
  }

In adapter.js, I have set up the following functions to map columns:

    mapRealmPropertyName: (class_name, property_name) => {
        let result = property_name.replace(/\.?([A-Z])/g, (x, y) => {
            return `_${y.toLowerCase()}`;
        }).replace(/^_/, '');
        return result;
    },

    mapPostgresColumnName: (table_name, column_name) => {
        let result = column_name.replace(/_{1}[(a-z)]/g, (x) =>{
            return x.toUpperCase().replace(/_/, '');
        });
        return result;
    }

And here is a screenshot of the exception:


Edit: This screenshot turned out a little small, the most important bit here is the operation, which is the following:

{"op": "DELETE", "table": "Branch", "props":{"branch_code": "NINJA"}}

Looking at the code in node_modules/realm-postgres-adapters/dist, I found the following in the process_postgres_ops function in PostgresAdapter.js:

//excluding the portion that handles op === 'INSERT' and op === 'UPDATE'
else if (op.op === 'DELETE') {
                            if (object_schema.postgresPrimaryKey)
                                realm.delete(realm.objects(object_schema.name).filtered(`${object_schema.postgresPrimaryKey} = $0`, op.props[object_schema.postgresPrimaryKey]));
                            else {
                                let toDelete = realm.objectForPrimaryKey(object_schema.name, op.props[object_schema.primaryKey]);
                                if (toDelete) {
                                    realm.delete(toDelete);
                                }
                                else {
                                    this.logger.warn(`Row has been deleted but could not find object with primary key ${op.props[object_schema.primaryKey]} of type ${object_schema.name} when trying to delete the object. This is not necessarily an error, and the delete operation will be ignored.`);
                                }
                            }
                        }

It seems to me that what happens with my current setup is it attempts realm.objectForPrimaryKey[‘Branch’, ops.props[‘branchCode’]), but since ops.props doesn’t has branch_code, not branchCode, it uses ‘undefined’ and thus throws the error.

So it seems my option would be to set a postgresPrimaryKey property in my model definition, but that doesn’t work either. Either i set it to ‘branch_code’ and get an error that that property doesn’t exist in the model, or I set it to ‘branchCode’ and no delete occurs, because no property exists with that name in op.props.

So it appears to me that although the adapter handles INSERT and UPDATE operations just fine with a mapping for column names to property names, DELETE does not, and expects them to be the same. Am I correct in assuming this is unintended behavior? I don’t recall reading anything about it in the documentation.

Thanks for your time, and I look forward to hearing your thoughts.


#2

Thank you for the detailed bug report. From your report I have been able to reproduce it. The bug is that the property name isn’t mapped to the column name (I can see that you have been thinking along the same lines).

I have fixed it and ask for review. I hope we will be able to do a release this week. You can manually change

let toDelete = realm.objectForPrimaryKey(object_schema.name, op.props[object_schema.primaryKey]);

to

let toDelete = realm.objectForPrimaryKey(object_schema.name, op.props[this.config.mapRealmPropertyName(object_schema.name, object_schema.primaryKey)]);

as a test.


#3

Works like a charm, thank you very much!


#4

Realm Postgres Adapter v1.9.0 contains the fix.