-
Notifications
You must be signed in to change notification settings - Fork 321
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
Implement Table.delete_rows
.
#7709
Conversation
Co-authored-by: GregoryTravis <greg.m.travis@gmail.com>
_ = [source_table, update_action, key_columns, error_on_missing_columns, on_problems] | ||
Error.throw (Illegal_Argument.Error "Table.update_rows modifies the underlying table, so it is only supported for Database tables - in-memory tables are immutable.") |
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 we might want this behavior to work.
It's effectively a find and replace operation.
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'm not sure this is a good idea. I appreciate a potential usefulness of such operation, but the practical API is vastly different.
In the Database, what we do is we mutably modify the target table (and return it back). This is a side-effecting operation. From now on, all references to the table will have updated data.
In in-memory, Tables are immutable, so all this would do is return a new table with modified contents. A bit like DB dry-run (but currently our dry-run returns the original table unchanged - so even here this does not match).
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.
We aim for our APIs to be compatible and mostly interchangeable between DB and in-memory, of course to the extent possible.
I'm afraid that introducing an operation which behaves in a similar but fundamentally different and subtle way is asking for trouble - when switching between in-memory and Database the users may not notice the subtle differences and get bad results.
I'm not completely sure. To be honest, the difference is indeed rather small and it feels like in practice in many workflows it will not be felt. So it seems like this could work.
But I think it can introduce hard to understand errors (with the unexpected behaviour change).
IMHO it is better to have a clear distinction between side-effecting WRITE operations (like this one, or writing to file etc. that update some _external state), and normal 'immutable' transformations creating a new modified data structure (which this would have to become for the in-memory approach to work).
If we really want the 'search and replace' behaviour, maybe we should introduce a separate method, that returns a new modified table with these changes applied? We could then implement it both for in-memory and Database and ensure that in both cases they do not modify the original table, but just return a new one (i.e. in Database it would be a modified query which does the replace, essentially a kind of a VIEW; I guess it may not be most efficient in that backend, but at least would be consistent).
What do you think? IMO creating a single operation that for one backend is a side-effecting WRITE and for another is an immutable TRANSFORM is risky. If anything, I'd simply create a separate operation for the TRANSFORM scenario.
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's a bit extreme, but we could append _write to every method that writes to the database, so it would always be clear.
@@ -457,6 +457,8 @@ generate_query dialect query = case query of | |||
Query.Drop_Table name if_exists -> | |||
maybe_if_exists = if if_exists then Builder.code "IF EXISTS " else Builder.empty | |||
Builder.code "DROP TABLE " ++ maybe_if_exists ++ dialect.wrap_identifier name | |||
Query.Truncate_Table name -> | |||
Builder.code "DELETE FROM " ++ dialect.wrap_identifier name |
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 TRUNCATE 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.
Not available in SQLite. I guess I could try to do it in Postgres.
I checked the following tests to see how presence of NULLs affects
The results are:
We can see that |
// Workaround for https://github.com/oracle/graal/issues/7359 | ||
if (!functionsLibrary.hasExecutableName(function)) { | ||
err.enter(); | ||
Builtins builtins = EnsoContext.get(this).getBuiltins(); | ||
throw new PanicException( | ||
builtins.error().makeTypeError(builtins.function(), function, "function"), 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.
Here's a, possibly temporary, workaround for oracle/graal#7359
Essentially, it is possible that getExecutableName
will not throw but still return null
, so a compiler error on null
will crash the interpreter.
builder.append ('\n <error message="' + escaped_message + '">\n') | ||
builder.append ('\n <failure message="' + escaped_message + '">\n') | ||
if details.is_nothing.not then | ||
## We duplicate the message, because sometimes the | ||
attribute is skipped if the node has any content. | ||
builder.append (escape_xml msg) | ||
builder.append '\n' | ||
builder.append (escape_xml details) | ||
builder.append '</error>\n' | ||
builder.append '</failure>\n' |
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 was testing visualizations of JUnit XML and it seems that failure
tag is more appropriate here.
name = Panic.catch Type_Error (Polyglot.get_executable_name el) _-> | ||
# Workaround for a bug where some stack frames' root nodes do not have a name. | ||
"<unknown node>" |
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.
This was sometimes failing, around SQL_Type_Reference.get
, do_fetch
and Connection.fetch_columns
. Whenever there were warnings attached to the query result, one entry in the stack trace had rootNode.getName() == null
and it was crashing the stack trace operation.
I think we better have a stack trace with an <unknown node>
entry, than prevent adding warnings.
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.
Ideally, I'd like to figure out why rootNode.getName() == null
in the first place, but I could not easily find a minimal example.
@JaroslavTulach any ideas?
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.
Great to see a fix proposed to GraalVM: oracle/graal#7360
Pull Request Description
Table.delete_rows
function allowing deleting rows in a database table. #7238update_database_table
to a more consistent and clearer API -update_rows
.truncate_table
helper function, to pair up withdrop_table
. Both arePRIVATE
for now.update_rows
anddelete_rows
.Null_Values_In_Key_Columns
.DefaultStackTraceElementObject.getExecutableName
breaks the contract ofInteropLibrary
oracle/graal#7359rootNode.getName() == null
).Important Notes
Checklist
Please ensure that the following checklist has been satisfied before submitting the PR:
Scala,
Java,
and
Rust
style guides. In case you are using a language not listed above, follow the Rust style guide.
./run ide build
.