Skip to content

Commit

Permalink
Validate json integrity (#92)
Browse files Browse the repository at this point in the history
* validate data integrity for json columns

* added comment

---------

Co-authored-by: Kevin Tabb <ktabb@Kevins-MacBook-Pro.local>
  • Loading branch information
K128kevin and Kevin Tabb committed Feb 6, 2023
1 parent ecdb48e commit 246d1f9
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 1 deletion.
23 changes: 22 additions & 1 deletion src/DataIntegrity.php
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,28 @@ public static function ensureFieldsPresent(dict<string, mixed> $row, table_schem
}
} else {
$field_value = (string)$row[$field_name];
if ($field_length > 0 && \mb_strlen($field_value) > $field_length) {

// handle json column type validation
if ($field_mysql_type === DataType::JSON) {
// null is okay
if ($row[$field_name] is nonnull) {
if (!Str\is_empty((string)$row[$field_name])) {
// validate json string
$json_obj = \json_decode((string)$row[$field_name]);
if ($json_obj is null) {
// invalid json
throw new SQLFakeRuntimeException(
"Invalid value '{$field_value}' for column '{$field_name}' on '{$schema['name']}', expected json",
);
}
} else {
// empty strings are not valid for json columns
throw new SQLFakeRuntimeException(
"Invalid value '{$field_value}' for column '{$field_name}' on '{$schema['name']}', expected json",
);
}
}
} else if ($field_length > 0 && \mb_strlen($field_value) > $field_length) {
$field_str = \var_export($row[$field_name], true);
throw new SQLFakeRuntimeException(
"Invalid value '{$field_str}' for column '{$field_name}' on '{$schema['name']}', expected string of size {$field_length}",
Expand Down
36 changes: 36 additions & 0 deletions tests/InsertQueryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,42 @@ final class InsertQueryTest extends HackTest {
);
}

public async function testEmptyStringInsertIntoJsonColumn(): Awaitable<void> {
$conn = static::$conn as nonnull;
QueryContext::$strictSQLMode = true;
expect(() ==> $conn->query("INSERT INTO table_with_json (id, data) VALUES (1, '')"))
->toThrow(
SQLFakeRuntimeException::class,
"Invalid value '' for column 'data' on 'table_with_json', expected json",
);
}

public async function testInvalidJsonStringInsertIntoJsonColumn(): Awaitable<void> {
$conn = static::$conn as nonnull;
QueryContext::$strictSQLMode = true;
expect(() ==> $conn->query("INSERT INTO table_with_json (id, data) VALUES (1, 'abc')"))
->toThrow(
SQLFakeRuntimeException::class,
"Invalid value 'abc' for column 'data' on 'table_with_json', expected json",
);
}

public async function testNullInsertIntoJsonColumn(): Awaitable<void> {
$conn = static::$conn as nonnull;
QueryContext::$strictSQLMode = true;
await $conn->query("INSERT INTO table_with_json (id, data) VALUES (1, NULL)");
$result = await $conn->query("SELECT * FROM table_with_json");
expect($result->rows())->toBeSame(vec[dict['id' => 1, 'data' => null]]);
}

public async function testValidJsonInsertIntoJsonColumn(): Awaitable<void> {
$conn = static::$conn as nonnull;
QueryContext::$strictSQLMode = true;
await $conn->query("INSERT INTO table_with_json (id, data) VALUES (1, '{\"test\":123}')");
$result = await $conn->query("SELECT * FROM table_with_json");
expect($result->rows())->toBeSame(vec[dict['id' => 1, 'data' => '{"test":123}']]);
}

public async function testDupeInsertNoConflicts(): Awaitable<void> {
$conn = static::$conn as nonnull;
await $conn->query(
Expand Down
26 changes: 26 additions & 0 deletions tests/SharedSetup.php
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,32 @@ final class SharedSetup {
),
],
),
'table_with_json' => shape(
'name' => 'table_with_json',
'fields' => vec[
shape(
'name' => 'id',
'type' => DataType::BIGINT,
'length' => 20,
'null' => false,
'hack_type' => 'int',
),
shape(
'name' => 'data',
'type' => DataType::JSON,
'length' => 255,
'null' => true,
'hack_type' => 'string',
),
],
'indexes' => vec[
shape(
'name' => 'PRIMARY',
'type' => 'PRIMARY',
'fields' => keyset['id'],
)
],
),
'table_with_more_fields' => shape(
'name' => 'table_with_more_fields',
'fields' => vec[
Expand Down

0 comments on commit 246d1f9

Please sign in to comment.