@@ -52,7 +52,7 @@ def get_querystring(
5252 value : t .Union [str , Varchar , Text ],
5353 engine_type : str ,
5454 reverse = False ,
55- ):
55+ ) -> QueryString :
5656 Concat = ConcatPostgres if engine_type == "postgres" else ConcatSQLite
5757
5858 if isinstance (value , (Varchar , Text )):
@@ -106,7 +106,7 @@ def get_querystring(
106106 operator : str ,
107107 value : t .Union [int , float , Integer ],
108108 reverse = False ,
109- ):
109+ ) -> QueryString :
110110 if isinstance (value , Integer ):
111111 column : Integer = value
112112 if len (column ._meta .call_chain ) > 0 :
@@ -1268,8 +1268,9 @@ def arrow(self, key: str) -> JSONB:
12681268 Allows part of the JSON structure to be returned - for example,
12691269 for {"a": 1}, and a key value of "a", then 1 will be returned.
12701270 """
1271- self .json_operator = f"-> '{ key } '"
1272- return self
1271+ instance = t .cast (JSONB , self .copy ())
1272+ instance .json_operator = f"-> '{ key } '"
1273+ return instance
12731274
12741275 def get_select_string (self , engine_type : str , just_alias = False ) -> str :
12751276 select_string = self ._meta .get_full_name (just_alias = just_alias )
@@ -1343,3 +1344,95 @@ class Blob(Bytea):
13431344 """
13441345
13451346 pass
1347+
1348+
1349+ ###############################################################################
1350+
1351+
1352+ class Array (Column ):
1353+ """
1354+ Used for storing lists of data.
1355+
1356+ **Example**
1357+
1358+ .. code-block:: python
1359+
1360+ class Ticket(Table):
1361+ seat_numbers = Array(base_column=Integer())
1362+
1363+ # Create
1364+ >>> Ticket(seat_numbers=[34, 35, 36]).save().run_sync()
1365+
1366+ # Query
1367+ >>> Ticket.select(Ticket.seat_numbers).run_sync()
1368+ {'seat_numbers': [34, 35, 36]}
1369+
1370+ """
1371+
1372+ value_type = list
1373+
1374+ def __init__ (
1375+ self ,
1376+ base_column : Column ,
1377+ default : t .Union [t .List , t .Callable [[], t .List ], None ] = list ,
1378+ ** kwargs ,
1379+ ) -> None :
1380+ if isinstance (base_column , ForeignKey ):
1381+ raise ValueError ("Arrays of ForeignKeys aren't allowed." )
1382+
1383+ self ._validate_default (default , (list , None ))
1384+
1385+ self .base_column = base_column
1386+ self .default = default
1387+ self .index : t .Optional [int ] = None
1388+ kwargs .update ({"base_column" : base_column , "default" : default })
1389+ super ().__init__ (** kwargs )
1390+
1391+ @property
1392+ def column_type (self ):
1393+ engine_type = self ._meta .table ._meta .db .engine_type
1394+ if engine_type == "postgres" :
1395+ return f"{ self .base_column .column_type } []"
1396+ elif engine_type == "sqlite" :
1397+ return "ARRAY"
1398+ raise Exception ("Unrecognized engine type" )
1399+
1400+ def __getitem__ (self , value : int ) -> Array :
1401+ """
1402+ Allows queries which retrieve an item from the array. The index starts
1403+ with 0 for the first value. If you were to write the SQL by hand, the
1404+ first index would be 1 instead:
1405+
1406+ https://www.postgresql.org/docs/current/arrays.html
1407+
1408+ However, we keep the first index as 0 to fit better with Python.
1409+
1410+ For example:
1411+
1412+ .. code-block:: python
1413+
1414+ >>> Ticket.select(Ticket.seat_numbers[0]).first().run_sync
1415+ {'seat_numbers': 325}
1416+
1417+
1418+ """
1419+ if isinstance (value , int ):
1420+ if value < 0 :
1421+ raise ValueError ("Only positive integers are allowed." )
1422+
1423+ instance = t .cast (Array , self .copy ())
1424+
1425+ # We deliberately add 1, as Postgres treats the first array element
1426+ # as index 1.
1427+ instance .index = value + 1
1428+ return instance
1429+ else :
1430+ raise ValueError ("Only integers can be used for indexing." )
1431+
1432+ def get_select_string (self , engine_type : str , just_alias = False ) -> str :
1433+ select_string = self ._meta .get_full_name (just_alias = just_alias )
1434+
1435+ if isinstance (self .index , int ):
1436+ return f"{ select_string } [{ self .index } ]"
1437+ else :
1438+ return select_string
0 commit comments