Skip to content
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

Fix to #28816 - Json: add support for Sqlite provider #30302

Merged
merged 1 commit into from
Feb 24, 2023
Merged

Fix to #28816 - Json: add support for Sqlite provider #30302

merged 1 commit into from
Feb 24, 2023

Conversation

maumar
Copy link
Contributor

@maumar maumar commented Feb 17, 2023

Adding support for Sqlite.

Limitation:
When accessing element of a JSON array we can only use constant values. Unlike Sql Server which supports parameters, columns or even arbitrary expressions.

Also since JSON aggregates are mapped to own types, in case of collections users need to ignore SqliteEventId.CompositeKeyWithValueGeneration

Fixes #28816

@maumar maumar requested review from a team, bricelam and roji February 17, 2023 00:09
@maumar maumar force-pushed the fix28816 branch 2 times, most recently from 7dd826e to 2c6e447 Compare February 17, 2023 00:16
@maumar maumar force-pushed the fix28816 branch 2 times, most recently from e5adba7 to 4e32563 Compare February 23, 2023 11:00
@maumar
Copy link
Contributor Author

maumar commented Feb 23, 2023

updated and ready for review @AndriySvyryd @roji @bricelam

// Sqlite converts true/false into native 0/1 when using json_extract
// so we convert those values to strings so that they stay as true/false
// which is what we want to store in json object in the end
var modifiedPropertyValue = boolPropertyValue
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there more "elegant" way to do this? i.e. without hard-coding the "true"/"false" values? @bricelam

Copy link
Member

@roji roji left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really nice that this required so little changes etc. - but see comments; I think we may need some more generalized infra, looking forward.

One additional note... SQLite supports two methods for extracting values out of JSON documents: json_extract and the ->/->> operators (which PG and MySQL support too). At least from a clean SQL perspective, these may be a better fit: instead of piecing together a JSONPATH string (and concatenating where non-constant data is provided), we can just use these operators. Compare the following two:

SELECT json_extract(json, '$.c[' || ind || ']') FROM data;
SELECT json->'$.c'->ind FROM data;

There's even a (theoretical) chance the -> would be faster, since we're expressing things more directly/simply, rather than concatenating arbitrary things to form the JSONPATH string.

Note that these operators were introduced in SQLite 3.38.0 (2022-02-22), though I don't think that should be an issue.

src/EFCore.Relational/Update/ModificationCommand.cs Outdated Show resolved Hide resolved
&& property.ClrType.UnwrapNullableType().IsInteger()
&& !HasConverter(property))
// JSON columns have no property mappings so all annotations that rely on property mappings should be skipped for them
if (column is not JsonColumn)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Over in SqlServerAnnotationProvider, most of the code isn't under a check against JsonColumn; for example, I can see the identity code doing it's thinking without checking for JSON.

Should we change things over in SQL Server too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

code in SqlServer now has null ref protection, so it should be fine. We used to access property like this:

var property = column.PropertyMappings.First().Property

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I see.

It would probably be a bit clearer if the entire SQL Server code block were wrapped in if (column is not JsonColumn) like this PR does for Sqlite, just to make it explicit exactly when we don't expect the code to be relevant.

/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public class SqliteJsonTypeMapping : JsonTypeMapping
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a copy-paste of SqlServerJsonTypeMapping; should it be in relational?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm ok to put the common logic in relational, but won't you need some sort of customization in npg? (e.g. two json type mappings one for json and one for jsonb).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, Npgsql would probably have two instances of this (it already accepts the store type in the constructor).

@@ -143,6 +145,64 @@ protected override void AppendRowsAffectedWhereCondition(StringBuilder commandSt
public override string GenerateNextSequenceValueOperation(string name, string? schema)
=> throw new NotSupportedException(SqliteStrings.SequencesNotSupported);


/// <inheritdoc />
protected override void AppendUpdateColumnValue(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we extract out an overridable AppendJsonModification method, called when columnModification.JsonPath is non-null? This would more cleanly separate JSON updates from non-JSON updates, and be the extension point for implementing partial updates etc.

// json_extract converts true/false into native 0/1 values,
// but we want to store the values as true/false in JSON
// in order to do that we modify the parameter value to "true"/"false"
// and wrap json() function around it to avoid conversion to 0/1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it enough that we're transforming the value into true/false strings in SqliteModificationCommand.GenerateJsonForSinglePropertyUpdate? The json() function is documented as "verifies that its argument X is a valid JSON string and returns a minified version of that JSON string"; is it somehow preventing a conversion here or something?

Copy link
Contributor Author

@maumar maumar Feb 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, without json around the call the value after update is stored as:

"TestBoolean":"true"

whereas before (and what we ultimately want) is:

"TestBoolean":true

Funny enough, select json_extract('["true"]', '$[0]'); and select json(json_extract('["true"]', '$[0]')); seem to return the same result: true

¯\_(ツ)_/¯

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK...

I think this also related to #30302 (comment); if we had a more general thing that says how to create a literal JSON from the value, we'd just be able to directly get true out, rather than having to jump through hoops and use server-side logic to convert stuff. Unless I'm mistaken, this would allow us to remove the CAST in SQL Server and the json_extract/json here in SQLite.

Adding support for Sqlite.
Also adding some more query and update tests for properties with value converters.

Fixes #28816
Copy link
Contributor

@bricelam bricelam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Json: add support for Sqlite provider
4 participants