Skip to content

Commit

Permalink
allow alternative fk syntax (#1005)
Browse files Browse the repository at this point in the history
{ "remote_schema": "s2", "remote_table": "t2", "local_to_remote_columns": { "x": "a", "y": "b", ... }

{"local_columns": ["x", "y", ...]}

{ "remote_columns": [ "a", "b" ], "remote_schema": "s2", "remote_table": "t2"}
  • Loading branch information
RFSH authored Feb 26, 2024
1 parent a2aacb6 commit a42ec41
Show file tree
Hide file tree
Showing 7 changed files with 311 additions and 38 deletions.
37 changes: 37 additions & 0 deletions docs/dev-docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,9 @@ to use for ERMrest JavaScript agents.
* [.pureBinaryForeignKeys](#ERMrest.Table+pureBinaryForeignKeys) : [<code>Array.&lt;ForeignKeyRef&gt;</code>](#ERMrest.ForeignKeyRef)
* [._getRowDisplayKey(context)](#ERMrest.Table+_getRowDisplayKey)
* [._getNullValue()](#ERMrest.Table+_getNullValue) : <code>object</code>
* [._findForeignKeyByRemoteColumns(remoteTable, remoteColumnNames, nameMapping)](#ERMrest.Table+_findForeignKeyByRemoteColumns)
* [~matchFk()](#ERMrest.Table+_findForeignKeyByRemoteColumns..matchFk)
* [.findForeignKey()](#ERMrest.Table+findForeignKey)
* [._assignAssetCategories()](#ERMrest.Table+_assignAssetCategories)
* _static_
* [.Entity](#ERMrest.Table.Entity)
Expand Down Expand Up @@ -1281,6 +1284,9 @@ check for table name existence
* [.pureBinaryForeignKeys](#ERMrest.Table+pureBinaryForeignKeys) : [<code>Array.&lt;ForeignKeyRef&gt;</code>](#ERMrest.ForeignKeyRef)
* [._getRowDisplayKey(context)](#ERMrest.Table+_getRowDisplayKey)
* [._getNullValue()](#ERMrest.Table+_getNullValue) : <code>object</code>
* [._findForeignKeyByRemoteColumns(remoteTable, remoteColumnNames, nameMapping)](#ERMrest.Table+_findForeignKeyByRemoteColumns)
* [~matchFk()](#ERMrest.Table+_findForeignKeyByRemoteColumns..matchFk)
* [.findForeignKey()](#ERMrest.Table+findForeignKey)
* [._assignAssetCategories()](#ERMrest.Table+_assignAssetCategories)
* _static_
* [.Entity](#ERMrest.Table.Entity)
Expand Down Expand Up @@ -1499,6 +1505,37 @@ sort the "well formed" keys that are not simple fk based on the following and re
return the null value that should be shown for the columns under
this table for the given context.

**Kind**: instance method of [<code>Table</code>](#ERMrest.Table)
<a name="ERMrest.Table+_findForeignKeyByRemoteColumns"></a>

#### table.\_findForeignKeyByRemoteColumns(remoteTable, remoteColumnNames, nameMapping)
return the fk that is related to the remote table and column names (it could be inbound or outbound)

**Kind**: instance method of [<code>Table</code>](#ERMrest.Table)

| Param | Type |
| --- | --- |
| remoteTable | <code>string</code> |
| remoteColumnNames | <code>Array.&lt;string&gt;</code> |
| nameMapping | <code>Object</code> |

<a name="ERMrest.Table+_findForeignKeyByRemoteColumns..matchFk"></a>

##### _findForeignKeyByRemoteColumns~matchFk()
inbound: from = remote, to = local
outbound: from = local, to = remote

**Kind**: inner method of [<code>\_findForeignKeyByRemoteColumns</code>](#ERMrest.Table+_findForeignKeyByRemoteColumns)
<a name="ERMrest.Table+findForeignKey"></a>

#### table.findForeignKey()
find the foreignkey that is part of this table, or has a path to it.

{ "remote_schema": "s2", "remote_table": "t2", "local_to_remote_columns": { "x": "a", "y": "b", ... }
{ "remote_schema": "s2", "remote_table": "t2", "remote_columns": [ "a", "b" ]}

{"local_columns": ["x", "y", ...]}

**Kind**: instance method of [<code>Table</code>](#ERMrest.Table)
<a name="ERMrest.Table+_assignAssetCategories"></a>

Expand Down
152 changes: 152 additions & 0 deletions js/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -2180,6 +2180,158 @@
return module._getNullValue(this, context, true);
},

/**
* return the fk that is based on the given column names (it could be inbound or outbound)
* @param {string[]} localColumnNames
* @private
*/
_findForeignKeyByLocalColumns: function (localColumnNames) {
if (!Array.isArray(localColumnNames) || localColumnNames.length === 0) {
return {successful: false, message: 'local_columns must be a non-empty array.'};
}
var matchFk = function (isInbound) {
return function (fk) {
// same length
if (fk.colset.length() !== localColumnNames.length) return false;

/**
* find the fks with the given local columns
*
* inbound: local=to, remote=from
* outbound: local=from, remote=to
*/
var fkCols = isInbound ? fk.key.colset.columns : fk.colset.columns;
return fkCols.every(function (col) {
return localColumnNames.indexOf(col.name) !== -1;
});
};
};

var fks = this.foreignKeys.all().filter(matchFk(false));
if (fks.length > 1) {
return {successful: false, message: 'more than one foreign key matches.'};
}
var ifks = this.referredBy.all().filter(matchFk(true));
if (ifks.length > 1 || (fks.length > 0 && ifks.length > 0)) {
return {successful: false, message: 'more than one foreign key matches.'};
}
if (fks.length === 0 && ifks.length === 0) {
return {successful: false, message: 'couldn\'t find any matching foreign key.'};
}
return {
successful: true,
foreignKey: fks.length > 0 ? fks[0] : ifks[0],
isInbound: ifks.length > 0
};
},

/**
* return the fk that is related to the remote table and column names (it could be inbound or outbound)
* @param {string} remoteTable
* @param {string[]} remoteColumnNames
* @param {Object} nameMapping
*/
_findForeignKeyByRemoteColumns: function (remoteTable, remoteColumnNames, nameMapping) {
if (!Array.isArray(remoteColumnNames) || remoteColumnNames.length === 0) {
return {successful: false, message: 'remote_columns or local_to_remote_columns must be defined properly.'};
}

/**
* inbound: from = remote, to = local
* outbound: from = local, to = remote
*/
var matchFk = function (isInbound) {
return function (fk) {
// same length
if (fk.colset.length() !== remoteColumnNames.length) return false;

// end up in the same table
if ((!isInbound && fk.key.table !== remoteTable) || (isInbound && fk.table !== remoteTable)) {
return false;
}

var fkCols;
if (isObjectAndNotNull(nameMapping)) {
// making sure the column mapping is correct
// we want the loop to always be based on "local". so in inbound, we get the "to" and in outbound "from".
fkCols = isInbound ? fk.key.colset.columns : fk.colset.columns;
return fkCols.every(function (localCol) {
if (!(localCol.name in nameMapping)) return false;

var remoteCol = isInbound ? fk.mapping.getFrom(localCol) : fk.mapping.get(localCol);
return nameMapping[localCol.name] === remoteCol.name;
});
} else {
// making sure remote column names are valid
fkCols = isInbound ? fk.colset.columns : fk.key.colset.columns;
return fkCols.every(function (col) {
return remoteColumnNames.indexOf(col.name) !== -1;
});
}
};
};

var fks = this.foreignKeys.all().filter(matchFk(false));
if (fks.length > 1) {
return {successful: false, message: 'more than one foreign key matches.'};
}
var ifks = this.referredBy.all().filter(matchFk(true));
if (ifks.length > 1 || (fks.length > 0 && ifks.length > 0)) {
return {successful: false, message: 'more than one foreign key matches.'};
}
if (fks.length === 0 && ifks.length === 0) {
return {successful: false, message: 'couldn\'t find any matching foreign key.'};
}
return {
successful: true,
foreignKey: fks.length > 0 ? fks[0] : ifks[0],
isInbound: ifks.length > 0
};
},

/**
* find the foreignkey that is part of this table, or has a path to it.
*
* { "remote_schema": "s2", "remote_table": "t2", "local_to_remote_columns": { "x": "a", "y": "b", ... }
* { "remote_schema": "s2", "remote_table": "t2", "remote_columns": [ "a", "b" ]}
*
* {"local_columns": ["x", "y", ...]}
*
*/
findForeignKey: function (obj) {
if (!isObjectAndNotNull(obj)) {
return {successful: false, message: 'invalid object.'};
}
var self = this;

if (self.foreignKeys.length() === 0 && self.referredBy.length() === 0) {
return {successful: false, message: 'the local table doesn\'t have any inbound or outbound fks.'};
}

if (isStringAndNotEmpty(obj.remote_schema) && isStringAndNotEmpty(obj.remote_table)) {
var remoteTable;
try {
remoteTable = self.schema.catalog.schemas.findTable(obj.remote_table, obj.remote_schema);
} catch (exp) {
return {successful: false, message: 'remote table not found.'};
}

if (Array.isArray(obj.remote_columns)) {
return self._findForeignKeyByRemoteColumns(remoteTable, obj.remote_columns);
}
else if (isObjectAndNotNull(obj.local_to_remote_columns)) {
return self._findForeignKeyByRemoteColumns(remoteTable, Object.values(obj.local_to_remote_columns), obj.local_to_remote_columns);
} else {
return {successful: false, message: 'remote_columns or local_to_remote_columns must be defined properly.'};
}
}
else if (Array.isArray(obj.local_columns)) {
return self._findForeignKeyByLocalColumns(obj.local_columns);
}

return {successful: false, message: 'none of the acceptable combination was used.'};
},

/**
* returns an object that captures the asset category of columns.
* - the key of the returned object is the column name and value and the value is an object. The object has the following:
Expand Down
1 change: 1 addition & 0 deletions js/reference.js
Original file line number Diff line number Diff line change
Expand Up @@ -4901,6 +4901,7 @@
// facetColumns is applying extra logic for alternative, and it only
// makes sense in the context of facetColumns list. not here.
// Therefore we should go based on the facets on the location object, not facetColumns.
// TODO should be modified to support the alternative syntaxes
var modifyFacetFilters = function (funct) {
currentFacets.forEach(function (f) {
if (!f.source) return;
Expand Down
14 changes: 13 additions & 1 deletion js/utils/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,11 @@
"alias": "a",
"inbound": "i",
"outbound": "o",
'remote_schema': 'r_s',
'remote_table': 'r_t',
'remote_columns': 'r_c',
'local_columns': 'l_c',
'local_to_remote_columns': 'n_to_f_c',
"source": "src",
"sourcekey": "key",
"choices": "ch",
Expand All @@ -433,7 +438,14 @@
OR: "or",
OPERATOR: "operator",
OPERAND_PATTERN: "operand_pattern",
NEGATE: "negate"
NEGATE: "negate",
LOCAL_SCHEMA: 'local_schema',
LOCAL_TABLE: 'local_table',
REMOTE_SCHEMA: 'remote_schema',
REMOTE_TABLE: 'remote_table',
LOCAL_COLUMNS: 'local_columns',
REMOTE_COLUMNS: 'remote_columns',
LOCAL_TO_REMOTE_COLUMNS: 'local_to_remote_columns'
});

module._exportKnownAPIs = ['entity','attribute', 'attributegroup', 'aggregate'];
Loading

0 comments on commit a42ec41

Please sign in to comment.