-
-
Notifications
You must be signed in to change notification settings - Fork 338
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add link Table API #1374
Add link Table API #1374
Conversation
…er and operations to created the link
…nstead of abbreviated names.
Codecov Report
@@ Coverage Diff @@
## master #1374 +/- ##
==========================================
+ Coverage 92.99% 93.03% +0.04%
==========================================
Files 120 123 +3
Lines 4865 4982 +117
==========================================
+ Hits 4524 4635 +111
- Misses 341 347 +6
Flags with carried forward coverage won't be shown. Click here to find out more.
Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left some specific comments. The big one to deal with is about creating the foreign key columns as a single step.
db/links/operations/create.py
Outdated
} | ||
ctx = MigrationContext.configure(conn, opts=opts) | ||
op = Operations(ctx) | ||
op.create_table(map_table_name, schema=schema) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you should use the standard Mathesar function for creating a table. If nothing else, we'll want the default columns so this fits into the rest of the framework, correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
create_mathesar_table
doesn't respect the transaction. The below test case fails due to create_table
not being part of the transaction and ends up creating the tables although the foreign key constraint step fails. I am not sure if I am doing something wrong
def test_table_creation(engine_with_schema):
engine, schema = engine_with_schema
supported_types = get_supported_alter_column_types(
engine, friendly_names=False,
)
sa_type = supported_types.get("INTEGER")
metadata = MetaData(bind=engine, schema=schema, naming_convention=naming_convention)
with engine.begin() as conn:
metadata.reflect()
opts = {
'target_metadata': metadata
}
ctx = MigrationContext.configure(conn, opts=opts)
referent_column = MathesarColumn(
"reference",
sa_type()
)
target_table = create_mathesar_table(
"target_table",
schema,
[referent_column],
engine,
metadata,
)
fk_column = MathesarColumn(
"fk_col",
sa_type()
)
base_table = create_mathesar_table(
"base_table",
schema,
[fk_column],
engine,
metadata
)
op = Operations(ctx)
# op.create_unique_constraint(None, target_table.name, [referent_column.name], schema=schema)
try:
op.create_foreign_key(
None, base_table.name,
target_table.name,
[fk_column.name], [referent_column.name], source_schema=schema, referent_schema=schema
)
except:
pass
schema_obj = reflect_schema(engine, schema)
tables = get_table_oids_from_schema(schema_obj.oid, engine)
# Table should not be created as foreign key creation step would have failed due to referent column missing the unique constraint
assert len(tables) == 0
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The transaction fails if create_mathesar_table
uses a connection to bind instead of engine when creating the table
https://github.com/centerofci/mathesar/blob/b2467dcf63de786c0e8063821ff317ef608ec3a6/db/tables/operations/create.py#L28
table.create(engine)
table.create(connection)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Argh, what a pain.
I think it would still be better to use the create_mathesar_table
function to create the table, since it's important that we have the pieces needed (id
column at a minimum). We should then file a bug to deal with the fact that transactions aren't working properly.
I really want to avoid ad-hoc table creation calls scattered through the codebase since we already have a default column involved and we're going to add more default metadata, etc. over time.
db/links/operations/create.py
Outdated
column = MathesarColumn( | ||
column_name, primary_key_column.type | ||
) | ||
op.add_column(referrer_table.name, column, schema=schema) | ||
if unique_link: | ||
op.create_unique_constraint(None, referrer_table.name, [column_name], schema=schema) | ||
op.create_foreign_key( | ||
None, | ||
referrer_table.name, | ||
referent_table.name, | ||
[column.name], | ||
[primary_key_column.name], | ||
source_schema=schema, | ||
referent_schema=schema | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless there's a convincing reason not to, I think you should create the column with the unique constraint and foreign key included, then add that column to the table. This would make this simpler and more aligned with the foreign key creation in tb/tables/operations/split.py
. I can think of no reason to do it in separate operations given that we're not supporting multi-column foreign key creation with this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't creating a foreign key constraint in individual steps much more flexible, as it could allow for multiple columns, although we don't use it now. Does creating a Foreign key as part of the column bring in any advantage?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the main advantage is that it's easier to see the logic. I acknowledge, however, that it's more flexible in separate steps. If you want to keep separate steps, I can go with it.
db/links/operations/create.py
Outdated
for referent in referents: | ||
referent_table_oid = referent['referent_table'] | ||
referent_table = referent_tables[referent_table_oid] | ||
col_name = referent['column_name'] | ||
primary_key_column = get_primary_key_column(referent_table) | ||
column = MathesarColumn( | ||
col_name, primary_key_column.type | ||
) | ||
op.add_column(map_table_name, column, schema=schema) | ||
op.create_foreign_key( | ||
None, | ||
map_table_name, | ||
referent_table.name, | ||
[column.name], | ||
[primary_key_column.name], | ||
source_schema=schema, | ||
referent_schema=schema | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not use your create_foreign_key_link
function? Nested transactions don't commit until the outermost transaction is complete. If there's a reason not to call the other function, the comment from above about creating the column with the needed pieces then adding it applies here as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seemed redundant as both the signature are the same, but I agree it would be better to use a common API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In that case, consider extracting the common code from the two functions and using it as a private method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@silentninja I'm not able to get any requests to succeed, as spec'ed in your schema. Below are four scenarios that I've tried. Each scenario lists the request I sent. In each scenario, I expect a success response. And in each scenario, I observed an error (with the exact response shown that I received).
To me it seems like there are probably a number of small things in this PR that need to be adjusted in order for my API requests to succeed. For example, I'd like to use the property reference_table_id
instead of reference_table
. And in the "one-to-one" case, I think we should be specifying reference_column_name
not referent_column_name
.
One to one
Request:
{
"link_type": "one-to-one",
"reference_table_id": 4,
"reference_column_name": "planet_id_1",
"referent_table_id": 2
}
Response:
[
{
"code": 2002,
"field": "referent_column_name",
"message": "This field is required.",
"detail": {}
},
{
"code": 2007,
"field": "reference_table",
"message": "This field is required.",
"detail": {}
},
{
"code": 2007,
"field": "referent_table",
"message": "This field is required.",
"detail": {}
}
]
Many to one
Request:
{
"link_type": "many-to-one",
"reference_table_id": 4,
"reference_column": { "name": "planet_id_1" },
"referent_table_id": 2
}
Response:
[
{
"code": 4999,
"message": "'NoneType' object has no attribute 'create'",
"field": null,
"detail": null
}
]
One to many
Request:
{
"link_type": "one-to-many",
"reference_table_id": 4,
"referent_table_id": 2,
"referent_column_name": "moon_id"
}
Response:
[
{
"code": 2007,
"field": "reference_table",
"message": "This field is required.",
"detail": {}
},
{
"code": 2007,
"field": "referent_table",
"message": "This field is required.",
"detail": {}
}
]
Many to many
Request:
{
"link_type": "many-to-many",
"mapping_table_name": "moon_planet",
"referents": [
{ "table_id": 4, "column_name": "moon_id" },
{ "table_id": 2, "column_name": "planet_id" }
]
}
Response:
[
{
"code": 4999,
"message": "unhashable type: 'dict'",
"field": null,
"detail": null
}
]
|
…_name` in test cases
…thesar-972-link-table
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only blocking change I have to request is to figure out how to avoid the bare table creation. I think it's already a problem since it's not created with the default column(s), and it may get further from how other tables are created over time. The other request is a suggestion.
db/links/operations/create.py
Outdated
} | ||
ctx = MigrationContext.configure(conn, opts=opts) | ||
op = Operations(ctx) | ||
op.create_table(map_table_name, schema=schema) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Argh, what a pain.
I think it would still be better to use the create_mathesar_table
function to create the table, since it's important that we have the pieces needed (id
column at a minimum). We should then file a bug to deal with the fact that transactions aren't working properly.
I really want to avoid ad-hoc table creation calls scattered through the codebase since we already have a default column involved and we're going to add more default metadata, etc. over time.
db/links/operations/create.py
Outdated
for referent in referents: | ||
referent_table_oid = referent['referent_table'] | ||
referent_table = referent_tables[referent_table_oid] | ||
col_name = referent['column_name'] | ||
primary_key_column = get_primary_key_column(referent_table) | ||
column = MathesarColumn( | ||
col_name, primary_key_column.type | ||
) | ||
op.add_column(map_table_name, column, schema=schema) | ||
op.create_foreign_key( | ||
None, | ||
map_table_name, | ||
referent_table.name, | ||
[column.name], | ||
[primary_key_column.name], | ||
source_schema=schema, | ||
referent_schema=schema | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In that case, consider extracting the common code from the two functions and using it as a private method.
# Conflicts: # mathesar/api/exceptions/error_codes.py
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it looks good; let's get it merged!
Test cases for the requested changes are passing with the latest commits
Fixes #972
Adds APIs to support creating relationships between columns.
Technical details
As per the discussion in #972, the finalised APIs are
Checklist
Update index.md
).master
branch of the repositoryvisible errors.
Developer Certificate of Origin
Developer Certificate of Origin