@@ -889,7 +889,8 @@ public function createIndex(string $collection, string $id, string $type, array
889889 Database::INDEX_HNSW_COSINE ,
890890 Database::INDEX_HNSW_DOT => 'INDEX ' ,
891891 Database::INDEX_UNIQUE => 'UNIQUE INDEX ' ,
892- default => throw new DatabaseException ('Unknown index type: ' . $ type . '. Must be one of ' . Database::INDEX_KEY . ', ' . Database::INDEX_UNIQUE . ', ' . Database::INDEX_FULLTEXT . ', ' . Database::INDEX_SPATIAL . ', ' . Database::INDEX_HNSW_EUCLIDEAN . ', ' . Database::INDEX_HNSW_COSINE . ', ' . Database::INDEX_HNSW_DOT ),
892+ Database::INDEX_OBJECT => 'INDEX ' ,
893+ default => throw new DatabaseException ('Unknown index type: ' . $ type . '. Must be one of ' . Database::INDEX_KEY . ', ' . Database::INDEX_UNIQUE . ', ' . Database::INDEX_FULLTEXT . ', ' . Database::INDEX_SPATIAL . ', ' . Database::INDEX_OBJECT . ', ' . Database::INDEX_HNSW_EUCLIDEAN . ', ' . Database::INDEX_HNSW_COSINE . ', ' . Database::INDEX_HNSW_DOT ),
893894 };
894895
895896 $ key = "\"{$ this ->getNamespace ()}_ {$ this ->tenant }_ {$ collection }_ {$ id }\"" ;
@@ -908,6 +909,7 @@ public function createIndex(string $collection, string $id, string $type, array
908909 Database::INDEX_HNSW_EUCLIDEAN => " USING HNSW ( {$ attributes } vector_l2_ops) " ,
909910 Database::INDEX_HNSW_COSINE => " USING HNSW ( {$ attributes } vector_cosine_ops) " ,
910911 Database::INDEX_HNSW_DOT => " USING HNSW ( {$ attributes } vector_ip_ops) " ,
912+ Database::INDEX_OBJECT => " USING GIN ( {$ attributes }) " ,
911913 default => " ( {$ attributes }) " ,
912914 };
913915
@@ -1656,6 +1658,62 @@ protected function handleSpatialQueries(Query $query, array &$binds, string $att
16561658 }
16571659 }
16581660
1661+ /**
1662+ * Handle JSONB queries
1663+ *
1664+ * @param Query $query
1665+ * @param array<string, mixed> $binds
1666+ * @param string $attribute
1667+ * @param string $alias
1668+ * @param string $placeholder
1669+ * @return string
1670+ */
1671+ protected function handleObjectQueries (Query $ query , array &$ binds , string $ attribute , string $ alias , string $ placeholder ): string
1672+ {
1673+ switch ($ query ->getMethod ()) {
1674+ case Query::TYPE_EQUAL :
1675+ case Query::TYPE_NOT_EQUAL : {
1676+ $ isNot = $ query ->getMethod () === Query::TYPE_NOT_EQUAL ;
1677+ $ conditions = [];
1678+ foreach ($ query ->getValues () as $ key => $ value ) {
1679+ $ binds [": {$ placeholder }_ {$ key }" ] = json_encode ($ value );
1680+ $ fragment = "{$ alias }. {$ attribute } @> : {$ placeholder }_ {$ key }::jsonb " ;
1681+ $ conditions [] = $ isNot ? "NOT ( " . $ fragment . ") " : $ fragment ;
1682+ }
1683+ $ separator = $ isNot ? ' AND ' : ' OR ' ;
1684+ return empty ($ conditions ) ? '' : '( ' . implode ($ separator , $ conditions ) . ') ' ;
1685+ }
1686+
1687+ case Query::TYPE_CONTAINS :
1688+ case Query::TYPE_NOT_CONTAINS : {
1689+ $ isNot = $ query ->getMethod () === Query::TYPE_NOT_CONTAINS ;
1690+ $ conditions = [];
1691+ foreach ($ query ->getValues () as $ key => $ value ) {
1692+ if (count ($ value ) === 1 ) {
1693+ $ jsonKey = array_key_first ($ value );
1694+ $ jsonValue = $ value [$ jsonKey ];
1695+
1696+ // If scalar (e.g. "skills" => "typescript"),
1697+ // wrap it to express array containment: {"skills": ["typescript"]}
1698+ // If it's already an object/associative array (e.g. "config" => ["lang" => "en"]),
1699+ // keep as-is to express object containment.
1700+ if (!\is_array ($ jsonValue )) {
1701+ $ value [$ jsonKey ] = [$ jsonValue ];
1702+ }
1703+ }
1704+ $ binds [": {$ placeholder }_ {$ key }" ] = json_encode ($ value );
1705+ $ fragment = "{$ alias }. {$ attribute } @> : {$ placeholder }_ {$ key }::jsonb " ;
1706+ $ conditions [] = $ isNot ? "NOT ( " . $ fragment . ") " : $ fragment ;
1707+ }
1708+ $ separator = $ isNot ? ' AND ' : ' OR ' ;
1709+ return empty ($ conditions ) ? '' : '( ' . implode ($ separator , $ conditions ) . ') ' ;
1710+ }
1711+
1712+ default :
1713+ throw new DatabaseException ('Query method ' . $ query ->getMethod () . ' not supported for object attributes ' );
1714+ }
1715+ }
1716+
16591717 /**
16601718 * Get SQL Condition
16611719 *
@@ -1679,6 +1737,10 @@ protected function getSQLCondition(Query $query, array &$binds): string
16791737 return $ this ->handleSpatialQueries ($ query , $ binds , $ attribute , $ alias , $ placeholder );
16801738 }
16811739
1740+ if ($ query ->isObjectAttribute ()) {
1741+ return $ this ->handleObjectQueries ($ query , $ binds , $ attribute , $ alias , $ placeholder );
1742+ }
1743+
16821744 switch ($ query ->getMethod ()) {
16831745 case Query::TYPE_OR :
16841746 case Query::TYPE_AND :
@@ -1860,6 +1922,9 @@ protected function getSQLType(string $type, int $size, bool $signed = true, bool
18601922 case Database::VAR_DATETIME :
18611923 return 'TIMESTAMP(3) ' ;
18621924
1925+ case Database::VAR_OBJECT :
1926+ return 'JSONB ' ;
1927+
18631928 case Database::VAR_POINT :
18641929 return 'GEOMETRY(POINT, ' . Database::DEFAULT_SRID . ') ' ;
18651930
@@ -1873,7 +1938,7 @@ protected function getSQLType(string $type, int $size, bool $signed = true, bool
18731938 return "VECTOR( {$ size }) " ;
18741939
18751940 default :
1876- throw new DatabaseException ('Unknown Type: ' . $ type . '. Must be one of ' . Database::VAR_STRING . ', ' . Database::VAR_INTEGER . ', ' . Database::VAR_FLOAT . ', ' . Database::VAR_BOOLEAN . ', ' . Database::VAR_DATETIME . ', ' . Database::VAR_RELATIONSHIP . ', ' . Database::VAR_POINT . ', ' . Database::VAR_LINESTRING . ', ' . Database::VAR_POLYGON );
1941+ throw new DatabaseException ('Unknown Type: ' . $ type . '. Must be one of ' . Database::VAR_STRING . ', ' . Database::VAR_INTEGER . ', ' . Database::VAR_FLOAT . ', ' . Database::VAR_BOOLEAN . ', ' . Database::VAR_DATETIME . ', ' . Database::VAR_RELATIONSHIP . ', ' . Database::VAR_OBJECT . ' , ' . Database:: VAR_POINT . ', ' . Database::VAR_LINESTRING . ', ' . Database::VAR_POLYGON );
18771942 }
18781943 }
18791944
@@ -2106,6 +2171,16 @@ public function getSupportForSpatialAttributes(): bool
21062171 return true ;
21072172 }
21082173
2174+ /**
2175+ * Are object (JSONB) attributes supported?
2176+ *
2177+ * @return bool
2178+ */
2179+ public function getSupportForObject (): bool
2180+ {
2181+ return true ;
2182+ }
2183+
21092184 /**
21102185 * Does the adapter support null values in spatial indexes?
21112186 *
0 commit comments