diff --git a/build/psalm-baseline.xml b/build/psalm-baseline.xml
index 71d9fe016a4..369b1eebd28 100644
--- a/build/psalm-baseline.xml
+++ b/build/psalm-baseline.xml
@@ -2073,6 +2073,11 @@
+
+
+
+
+
@@ -2082,6 +2087,11 @@
+
+
+
+
+
@@ -3280,6 +3290,17 @@
request->server]]>
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/Migrations/Version33000Date20251106131209.php b/core/Migrations/Version33000Date20251106131209.php
index 488477694f4..9fab7d6d3c0 100644
--- a/core/Migrations/Version33000Date20251106131209.php
+++ b/core/Migrations/Version33000Date20251106131209.php
@@ -21,12 +21,19 @@ class Version33000Date20251106131209 extends SimpleMigrationStep {
private readonly IDBConnection $connection,
) {
}
+
public function preSchemaChange(IOutput $output, \Closure $schemaClosure, array $options) {
$qb = $this->connection->getQueryBuilder();
$qb->update('share')
->set('attributes', $qb->createNamedParameter('[["permissions","download",true]]'))
- ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_CIRCLE, IQueryBuilder::PARAM_INT)))
- ->andWhere($qb->expr()->eq('attributes', $qb->createNamedParameter('[["permissions","download",null]]', IQueryBuilder::PARAM_STR)));
+ ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_CIRCLE, IQueryBuilder::PARAM_INT)));
+
+ if ($this->connection->getDatabaseProvider(true) === IDBConnection::PLATFORM_MYSQL) {
+ $qb->andWhere($qb->expr()->eq('attributes', $qb->createFunction("JSON_ARRAY(JSON_ARRAY('permissions','download',null))"), IQueryBuilder::PARAM_JSON));
+ } else {
+ $qb->andWhere($qb->expr()->eq('attributes', $qb->createNamedParameter('[["permissions","download",null]]'), IQueryBuilder::PARAM_JSON));
+ }
+
$qb->executeStatement();
}
}
diff --git a/lib/private/DB/QueryBuilder/ExpressionBuilder/MySqlExpressionBuilder.php b/lib/private/DB/QueryBuilder/ExpressionBuilder/MySqlExpressionBuilder.php
index 7216fd8807b..0227c3154e3 100644
--- a/lib/private/DB/QueryBuilder/ExpressionBuilder/MySqlExpressionBuilder.php
+++ b/lib/private/DB/QueryBuilder/ExpressionBuilder/MySqlExpressionBuilder.php
@@ -44,6 +44,8 @@ class MySqlExpressionBuilder extends ExpressionBuilder {
switch ($type) {
case IQueryBuilder::PARAM_STR:
return new QueryFunction('CAST(' . $this->helper->quoteColumnName($column) . ' AS CHAR)');
+ case IQueryBuilder::PARAM_JSON:
+ return new QueryFunction('CAST(' . $this->helper->quoteColumnName($column) . ' AS JSON)');
default:
return parent::castColumn($column, $type);
}
diff --git a/lib/private/DB/QueryBuilder/ExpressionBuilder/OCIExpressionBuilder.php b/lib/private/DB/QueryBuilder/ExpressionBuilder/OCIExpressionBuilder.php
index 542e8d62ede..20308b24550 100644
--- a/lib/private/DB/QueryBuilder/ExpressionBuilder/OCIExpressionBuilder.php
+++ b/lib/private/DB/QueryBuilder/ExpressionBuilder/OCIExpressionBuilder.php
@@ -27,6 +27,32 @@ class OCIExpressionBuilder extends ExpressionBuilder {
return parent::prepareColumn($column, $type);
}
+ /**
+ * @inheritdoc
+ */
+ public function eq($x, $y, $type = null): string {
+ if ($type === IQueryBuilder::PARAM_JSON) {
+ $x = $this->prepareColumn($x, $type);
+ $y = $this->prepareColumn($y, $type);
+ return (string)(new QueryFunction('JSON_EQUAL(' . $x . ',' . $y . ')'));
+ }
+
+ return parent::eq($x, $y, $type);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function neq($x, $y, $type = null): string {
+ if ($type === IQueryBuilder::PARAM_JSON) {
+ $x = $this->prepareColumn($x, $type);
+ $y = $this->prepareColumn($y, $type);
+ return (string)(new QueryFunction('NOT JSON_EQUAL(' . $x . ',' . $y . ')'));
+ }
+
+ return parent::neq($x, $y, $type);
+ }
+
/**
* @inheritdoc
*/
diff --git a/lib/private/DB/QueryBuilder/ExpressionBuilder/PgSqlExpressionBuilder.php b/lib/private/DB/QueryBuilder/ExpressionBuilder/PgSqlExpressionBuilder.php
index 53a566a7eb6..f30fccadb1f 100644
--- a/lib/private/DB/QueryBuilder/ExpressionBuilder/PgSqlExpressionBuilder.php
+++ b/lib/private/DB/QueryBuilder/ExpressionBuilder/PgSqlExpressionBuilder.php
@@ -8,6 +8,8 @@
namespace OC\DB\QueryBuilder\ExpressionBuilder;
use OC\DB\QueryBuilder\QueryFunction;
+use OCP\DB\QueryBuilder\ILiteral;
+use OCP\DB\QueryBuilder\IParameter;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\DB\QueryBuilder\IQueryFunction;
@@ -25,12 +27,24 @@ class PgSqlExpressionBuilder extends ExpressionBuilder {
case IQueryBuilder::PARAM_INT:
return new QueryFunction('CAST(' . $this->helper->quoteColumnName($column) . ' AS BIGINT)');
case IQueryBuilder::PARAM_STR:
+ case IQueryBuilder::PARAM_JSON:
return new QueryFunction('CAST(' . $this->helper->quoteColumnName($column) . ' AS TEXT)');
default:
return parent::castColumn($column, $type);
}
}
+ /**
+ * @inheritdoc
+ */
+ protected function prepareColumn($column, $type) {
+ if ($type === IQueryBuilder::PARAM_JSON && !is_array($column) && !($column instanceof IParameter) && !($column instanceof ILiteral)) {
+ $column = $this->castColumn($column, $type);
+ }
+
+ return parent::prepareColumn($column, $type);
+ }
+
/**
* @inheritdoc
*/
diff --git a/lib/public/DB/QueryBuilder/IQueryBuilder.php b/lib/public/DB/QueryBuilder/IQueryBuilder.php
index 58f274702bb..b1c483522ee 100644
--- a/lib/public/DB/QueryBuilder/IQueryBuilder.php
+++ b/lib/public/DB/QueryBuilder/IQueryBuilder.php
@@ -99,6 +99,8 @@ interface IQueryBuilder {
/**
* @since 24.0.0
+ * @deprecated 33.0.0 JSON fields can not properly be used in WHERE statements of Oracle and MySQL.
+ * It is recommended to use a simple STRING field and handle JSON within PHP
*/
public const PARAM_JSON = 'json';
diff --git a/lib/public/DB/Types.php b/lib/public/DB/Types.php
index 969ec5e6611..f6221d98a89 100644
--- a/lib/public/DB/Types.php
+++ b/lib/public/DB/Types.php
@@ -174,6 +174,8 @@ final class Types {
/**
* @var string
* @since 24.0.0
+ * @deprecated 33.0.0 JSON fields can not properly be used in WHERE statements of Oracle and MySQL.
+ * It is recommended to use a simple STRING field and handle JSON within PHP
*/
public const JSON = 'json';
}
diff --git a/tests/lib/DB/QueryBuilder/ExpressionBuilderDBTest.php b/tests/lib/DB/QueryBuilder/ExpressionBuilderDBTest.php
index 112bfe2ca16..63f3ed4ab10 100644
--- a/tests/lib/DB/QueryBuilder/ExpressionBuilderDBTest.php
+++ b/tests/lib/DB/QueryBuilder/ExpressionBuilderDBTest.php
@@ -141,6 +141,55 @@ class ExpressionBuilderDBTest extends TestCase {
self::assertEquals('myvalue', $entries[0]['configvalue']);
}
+ public function testJson(): void {
+ if ($this->connection->getDatabaseProvider(true) === IDBConnection::PLATFORM_ORACLE) {
+ $result = $this->connection->executeQuery('SELECT VERSION FROM PRODUCT_COMPONENT_VERSION');
+ $version = $result->fetchOne();
+ $result->closeCursor();
+ if (str_starts_with($version, '11.')) {
+ $this->markTestSkipped('JSON is not supported on Oracle 11, skipping until deprecation was clarified: ' . $version);
+ }
+ }
+
+
+ $appId = $this->getUniqueID('testing');
+ $query = $this->connection->getQueryBuilder();
+ $query->insert('share')
+ ->values([
+ 'uid_owner' => $query->createNamedParameter('uid_owner'),
+ 'item_type' => $query->createNamedParameter('item_type'),
+ 'permissions' => $query->createNamedParameter(0),
+ 'stime' => $query->createNamedParameter(0),
+ 'accepted' => $query->createNamedParameter(0),
+ 'mail_send' => $query->createNamedParameter(0),
+ 'share_type' => $query->createNamedParameter(0),
+ 'share_with' => $query->createNamedParameter($appId),
+ 'attributes' => $query->createNamedParameter('[["permissions","before"]]'),
+ ])
+ ->executeStatement();
+
+ $query = $this->connection->getQueryBuilder();
+ $query->update('share')
+ ->set('attributes', $query->createNamedParameter('[["permissions","after"]]'));
+ if ($this->connection->getDatabaseProvider(true) === IDBConnection::PLATFORM_MYSQL) {
+ $query->where($query->expr()->eq('attributes', $query->createFunction("JSON_ARRAY(JSON_ARRAY('permissions','before'))"), IQueryBuilder::PARAM_JSON));
+ } else {
+ $query->where($query->expr()->eq('attributes', $query->createNamedParameter('[["permissions","before"]]'), IQueryBuilder::PARAM_JSON));
+ }
+ $query->executeStatement();
+
+ $query = $this->connection->getQueryBuilder();
+ $query->select('attributes')
+ ->from('share')
+ ->where($query->expr()->eq('share_with', $query->createNamedParameter($appId)));
+
+ $result = $query->executeQuery();
+ $entries = $result->fetchAll();
+ $result->closeCursor();
+ self::assertCount(1, $entries);
+ self::assertEquals([['permissions','after']], json_decode($entries[0]['attributes'], true));
+ }
+
public function testDateTimeEquals(): void {
$dateTime = new \DateTime('2023-01-01');
$insert = $this->connection->getQueryBuilder();