diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 2efb04e..e6c4029 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -4,6 +4,7 @@ on: [push]
jobs:
test:
runs-on: ubuntu-latest
+ name: Nextcloud ${{ matrix.nextcloud }} - ${{ matrix.db.name }}
strategy:
matrix:
db:
@@ -12,6 +13,11 @@ jobs:
username: root
database: nextcloud
port: 3306
+ - name: pgsql
+ image: postgres:17.5
+ username: postgres
+ database: postgres
+ port: 5432
nextcloud: [31]
services:
db:
@@ -54,5 +60,13 @@ jobs:
run: >
php occ -n fulltextsearch:configure '{"search_platform": "OCA\\FullTextSearch_SQL\\Platform\\SQLPlatform"}'
+ # This can be removed as soon as https://github.com/nextcloud/fulltextsearch/pull/915 is merged.
+ - name: Fix test harness
+ run: >
+ sed -i \
+ -e "s/'document is a simple test'/'document is a'/" \
+ -e "s/document is a simple -test/document -test/" \
+ custom_apps/fulltextsearch/lib/Command/Test.php
+
- name: Run fulltextsearch test
run: php occ -n fulltextsearch:test --platform_delay 1
\ No newline at end of file
diff --git a/appinfo/info.xml b/appinfo/info.xml
index df2b953..fcadcfd 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -29,7 +29,7 @@ Extension to the _Full text search_ app to communicate with the usual Nextcloud
mysql
- postgresql
+ pgsql
diff --git a/lib/Db/IndexDocumentMapper.php b/lib/Db/IndexDocumentMapper.php
index 23593a0..f647870 100644
--- a/lib/Db/IndexDocumentMapper.php
+++ b/lib/Db/IndexDocumentMapper.php
@@ -53,31 +53,43 @@ public function search(ISearchRequest $request, string $providerId, IDocumentAcc
);
}
- $qb->andWhere(
- $qb->expr()->orX(
- $qb->expr()->eq('owner', $qb->createNamedParameter($access->getViewerId(), IQueryBuilder::PARAM_STR)),
- 'JSON_CONTAINS(access_users, ' . $qb->createNamedParameter(json_encode($access->getViewerId())) . ')',
- 'JSON_OVERLAPS(access_groups, ' . $qb->createNamedParameter(json_encode($access->getGroups())) . ')',
- 'JSON_OVERLAPS(access_circles, ' . $qb->createNamedParameter(json_encode($access->getCircles())) . ')',
- )
- );
+ $search = $qb->createNamedParameter($request->getSearch(), IQueryBuilder::PARAM_STR);
+ $viewerId = $qb->createNamedParameter($access->getViewerId(), IQueryBuilder::PARAM_STR);
+ $jsonViewerId = $qb->createNamedParameter(json_encode($access->getViewerId()), IQueryBuilder::PARAM_STR);
+ $jsonGroups = $qb->createNamedParameter(json_encode($access->getGroups()), IQueryBuilder::PARAM_STR);
+ $jsonCircles = $qb->createNamedParameter(json_encode($access->getCircles()), IQueryBuilder::PARAM_STR);
+
// TODO: Match tags, subtags, whatnot...
switch ($this->db->getDatabaseProvider()) {
case IDBConnection::PLATFORM_MYSQL:
- $q = 'MATCH (content) AGAINST (:search IN BOOLEAN MODE)';
+ $qb->andWhere(
+ $qb->expr()->orX(
+ $qb->expr()->eq('owner', $viewerId),
+ "JSON_CONTAINS(access_users, $jsonViewerId)",
+ "JSON_OVERLAPS(access_groups, $jsonGroups)",
+ "JSON_OVERLAPS(access_circles, $jsonCircles)",
+ )
+ );
+
+ $q = "MATCH (content) AGAINST ($search IN BOOLEAN MODE)";
$qb->andWhere($q)
->selectAlias($qb->createFunction($q), 'score');
break;
+
case IDBConnection::PLATFORM_POSTGRES:
- $qb->andWhere('to_tsvector(content) @@ to_tsquery(:search)');
- break;
- case IDBConnection::PLATFORM_SQLITE:
- break;
- case IDBConnection::PLATFORM_ORACLE:
+ $qb
+ ->andWhere(
+ $qb->expr()->orX(
+ $qb->expr()->eq('owner', $viewerId),
+ "access_users @> $jsonViewerId::jsonb",
+ "jsonb_exists_any(access_groups, JSON_QUERY($jsonGroups, '$' RETURNING text[]))",
+ "jsonb_exists_any(access_circles, JSON_QUERY($jsonCircles, '$' RETURNING text[]))",
+ )
+ )
+ ->andWhere("to_tsvector(content) @@ websearch_to_tsquery($search)");
break;
}
- $qb->setParameter('search', $request->getSearch());
return $this->findEntities($qb);
}
diff --git a/lib/Migration/Version10000Date20250720000000.php b/lib/Migration/Version10000Date20250720000000.php
index dcd839b..17e0580 100644
--- a/lib/Migration/Version10000Date20250720000000.php
+++ b/lib/Migration/Version10000Date20250720000000.php
@@ -16,6 +16,10 @@
class Version10000Date20250720000000 extends SimpleMigrationStep {
public const TABLE = 'fts_documents';
+ private $collations = [
+ IDBConnection::PLATFORM_MYSQL => 'utf8mb4_unicode_ci',
+ IDBConnection::PLATFORM_POSTGRES => 'unicode',
+ ];
public function __construct(
private IDBConnection $db
@@ -44,28 +48,52 @@ public function changeSchema(IOutput $output, \Closure $schemaClosure, array $op
'notnull' => true
]);
$table->addColumn('access_users', Types::JSON, [
- 'notnull' => true
+ 'notnull' => true,
+ 'customSchemaOptions' => [
+ 'jsonb' => true,
+ ]
]);
$table->addColumn('access_circles', Types::JSON, [
- 'notnull' => true
+ 'notnull' => true,
+ 'customSchemaOptions' => [
+ 'jsonb' => true,
+ ]
]);
$table->addColumn('access_groups', Types::JSON, [
- 'notnull' => true
+ 'notnull' => true,
+ 'customSchemaOptions' => [
+ 'jsonb' => true,
+ ]
]);
$table->addColumn('access_links', Types::JSON, [
- 'notnull' => true
+ 'notnull' => true,
+ 'customSchemaOptions' => [
+ 'jsonb' => true,
+ ]
]);
$table->addColumn('tags', Types::JSON, [
- 'notnull' => true
+ 'notnull' => true,
+ 'customSchemaOptions' => [
+ 'jsonb' => true,
+ ]
]);
$table->addColumn('metadata', Types::JSON, [
- 'notnull' => true
+ 'notnull' => true,
+ 'customSchemaOptions' => [
+ 'jsonb' => true,
+ ]
]);
$table->addColumn('subtags', Types::JSON, [
- 'notnull' => true
+ 'notnull' => true,
+ 'customSchemaOptions' => [
+ 'jsonb' => true,
+ ]
]);
$table->addColumn('parts', Types::JSON, [
- 'notnull' => true
+ 'notnull' => true,
+ 'customSchemaOptions' => [
+ 'jsonb' => true,
+ ]
]);
$table->addColumn('link', Types::STRING, [
'notnull' => true
@@ -76,7 +104,7 @@ public function changeSchema(IOutput $output, \Closure $schemaClosure, array $op
$table->addColumn('content', Types::TEXT, [
'notnull' => true,
'customSchemaOptions' => [
- 'collation' => 'utf8mb4_unicode_ci'
+ 'collation' => $this->collations[$this->db->getDatabaseProvider()],
]
]);
$table->setPrimaryKey(['id']);