diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index e2ade71c..3006910a 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -9,6 +9,13 @@ const parseTypeToPostgresType = type => { case 'Date': return 'timestamp'; case 'Object': return 'jsonb'; case 'Boolean': return 'boolean'; + case 'Pointer': return 'char(10)'; + case 'Array': + if (type.contents && type.contents.type === 'String') { + return 'text[]'; + } else { + throw `no type for ${JSON.stringify(type)} yet`; + } default: throw `no type for ${JSON.stringify(type)} yet`; } }; @@ -50,7 +57,11 @@ export class PostgresStorageAdapter { let patternsArray = []; Object.keys(schema.fields).forEach((fieldName, index) => { valuesArray.push(fieldName); - valuesArray.push(parseTypeToPostgresType(schema.fields[fieldName])); + let parseType = schema.fields[fieldName]; + if (['_rperm', '_wperm'].includes(fieldName)) { + parseType.contents = { type: 'String' }; + } + valuesArray.push(parseTypeToPostgresType(parseType)); patternsArray.push(`$${index * 2 + 2}:name $${index * 2 + 3}:raw`); }); return this._client.query(`CREATE TABLE $1:name (${patternsArray.join(',')})`, [className, ...valuesArray]) @@ -128,7 +139,7 @@ export class PostgresStorageAdapter { } // Return a promise for all schemas known to this adapter, in Parse format. In case the - // schemas cannot be retrieved, returns a promise that rejects. Requirements for the + // schemas cannot be retrieved, returns a promise that rejects. Rquirements for the // rejection reason are TBD. getAllClasses() { return this._ensureSchemaCollectionExists() @@ -143,7 +154,7 @@ export class PostgresStorageAdapter { return this._client.query('SELECT * FROM "_SCHEMA" WHERE "className"=$', { className }) .then(result => { if (result.length === 1) { - return result; + return result[0]; } else { throw undefined; } @@ -152,24 +163,40 @@ export class PostgresStorageAdapter { // TODO: remove the mongo format dependency createObject(className, schema, object) { - console.log(object); let columnsArray = []; let valuesArray = []; Object.keys(object).forEach(fieldName => { columnsArray.push(fieldName); - valuesArray.push(object[fieldName]); + switch (schema.fields[fieldName].type) { + case 'Date': + valuesArray.push(object[fieldName].iso); + break; + case 'Pointer': + valuesArray.push(object[fieldName].objectId); + break; + default: + valuesArray.push(object[fieldName]); + break; + } }); - let columnsPattern = columnsArray.map((col, index) => `$${index + 2}~`).join(','); + let columnsPattern = columnsArray.map((col, index) => `$${index + 2}:name`).join(','); let valuesPattern = valuesArray.map((val, index) => `$${index + 2 + columnsArray.length}`).join(','); return this._client.query(`INSERT INTO $1~ (${columnsPattern}) VALUES (${valuesPattern})`, [className, ...columnsArray, ...valuesArray]) - .then(() => ({ ops: [object] })); + .then(() => ({ ops: [object] })) } // Remove all objects that match the given Parse Query. // If no objects match, reject with OBJECT_NOT_FOUND. If objects are found and deleted, resolve with undefined. // If there is some other error, reject with INTERNAL_SERVER_ERROR. deleteObjectsByQuery(className, schema, query) { - return Promise.reject('Not implented yet.') + return this._client.query(`WITH deleted AS (DELETE FROM $ RETURNING *) SELECT count(*) FROM deleted`, { className }) + .then(result => { + if (result[0].count === 0) { + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.'); + } else { + return result[0].count; + } + }); } // Apply the update to all objects that match the given Parse Query. @@ -190,6 +217,12 @@ export class PostgresStorageAdapter { // Executes a find. Accepts: className, query in Parse format, and { skip, limit, sort }. find(className, schema, query, { skip, limit, sort }) { return this._client.query("SELECT * FROM $", { className }) + .then(results => results.map(object => { + Object.keys(schema.fields).filter(field => schema.fields[field].type === 'Pointer').forEach(fieldName => { + object[fieldName] = { objectId: object[fieldName], __type: 'Pointer', className: schema.fields[fieldName].targetClass }; + }); + return object; + })) } // Create a unique index. Unique indexes on nullable fields are not allowed. Since we don't diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index a897dd22..e9bf04ac 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -410,7 +410,7 @@ DatabaseController.prototype.create = function(className, object, { acl } = {}) .then(() => this.handleRelationUpdates(className, null, object)) .then(() => schemaController.enforceClassExists(className)) .then(() => schemaController.getOneSchema(className, true)) - .then(schema => this.adapter.createObject(className, schema, object)) + .then(schema => this.adapter.createObject(className, SchemaController.convertSchemaToAdapterSchema(schema), object)) .then(result => sanitizeDatabaseResult(originalObject, result.ops[0])); }) }; diff --git a/src/Controllers/SchemaController.js b/src/Controllers/SchemaController.js index b8151526..d18f9523 100644 --- a/src/Controllers/SchemaController.js +++ b/src/Controllers/SchemaController.js @@ -223,6 +223,8 @@ const fieldTypeIsInvalid = ({ type, targetClass }) => { const convertSchemaToAdapterSchema = schema => { schema = injectDefaultSchema(schema); delete schema.fields.ACL; + schema.fields._rperm = { type: 'Array' }; + schema.fields._wperm = { type: 'Array' }; if (schema.className === '_User') { delete schema.fields.password; @@ -837,4 +839,5 @@ export { buildMergedSchemaObject, systemClasses, defaultColumns, + convertSchemaToAdapterSchema, }; diff --git a/src/RestQuery.js b/src/RestQuery.js index 38705287..349e4e14 100644 --- a/src/RestQuery.js +++ b/src/RestQuery.js @@ -526,16 +526,14 @@ function findPointers(object, path) { } if (typeof object !== 'object') { - throw new Parse.Error(Parse.Error.INVALID_QUERY, - 'can only include pointer fields'); + throw new Parse.Error(Parse.Error.INVALID_QUERY, 'can only include pointer fields'); } if (path.length == 0) { if (object.__type == 'Pointer') { return [object]; } - throw new Parse.Error(Parse.Error.INVALID_QUERY, - 'can only include pointer fields'); + throw new Parse.Error(Parse.Error.INVALID_QUERY, 'can only include pointer fields'); } var subobject = object[path[0]];