Scene 01
Inspect without executing
A query offers four properties to inspect it without executing it. sqltext is the SQL. sqlparams is the mapping of parameter values. test returns both, as a tuple. compiled is the CompiledQuery object. It carries the SQL, the parameters and the result columns. None of these opens a connection. The first access compiles the query, and the result is kept. sqlparams leaves out the env underscore parameters, because their values are read from the environment at every execution.
Example & screen notes
Slide title "Inspection". Code:
query = invoice.query('$id, $inv_number, $customer_id, $total',
'$total > :minimum', minimum=100, order_by='$inv_number')
query.sqltext # the SQL text
query.sqlparams # {'minimum': 100}
query.test() # (sqltext, sqlparams)
query.compiled # CompiledQuery: .sql, .params, .columns
Bullets:
- no connection is opened
- sqlparams does not list the :env_* values
Scene 02
fetch() and its rows
fetch executes the query and returns a list of rows. Each row is a NamedList from the genro_toolbox library. You read a value by name or by position. Negative positions work as in a list. keys, items, values, get and update work as in a dictionary. The in operator tests a column name. Iteration yields the values, not the names, because a NamedList is a list. dict of row gives a plain dictionary; use it for JSON or for comparisons. A row is mutable. Changing a value changes only the Python object, never the database. Rows of one fetch share their column index: a new name added to one row is visible to the others with the value None.
Example & screen notes
Slide title "fetch()". Code:
rows = query.fetch()
row = rows[0]
row['inv_number'], row[1], row[-1] # ('A2', 'A2', Decimal('500.00'))
list(row.keys()) # ['id', 'inv_number', 'customer_id', 'total']
row.get('missing') # None
'total' in row # True
list(row) # the values, not the names
dict(row) # {'id': ..., 'inv_number': 'A2', ...}
row['total'] = 0 # local change only
Scene 03
Each terminal executes again
A query keeps its compiled SQL, but not its rows. Every terminal executes the SQL again on the current connection. In this example the second fetch sees the invoice inserted after the first fetch, because it runs in the same transaction. No terminal commits. You end the transaction yourself, with commit or rollback.
Example & screen notes
Slide title "No cache". Code:
query = invoice.query('$inv_number')
len(query.fetch()) # 4
invoice.insert({'inv_number': 'C1', 'customer_id': ada_id, 'total': 1})
len(query.fetch()) # 5
db.rollback()
Scene 04
One record
fetchRecord returns exactly one row, in the same format as fetch. If no row matches, it raises RecordNotFoundError. With ignoreMissing equal True it returns None instead. If several rows match, it raises RecordMultipleRowsError. There is one exception. When the query has an explicit order_by, fetchRecord compiles a second version of the query with LIMIT one and returns the first row. The row policies of lesson eighteen apply as in every terminal, so a draft or deleted row is not found unless you change the query options.
Example & screen notes
Slide title "fetchRecord(ignoreMissing=False)". Code:
from asqueel import RecordNotFoundError, RecordMultipleRowsError
invoice.query('$inv_number, $total', '$inv_number = :n', n='A1').fetchRecord()
# {'inv_number': 'A1', 'total': Decimal('50.00')}
invoice.query('$inv_number', '$inv_number = :n', n='ZZ').fetchRecord()
# RecordNotFoundError: No visible record in invc.invoice
invoice.query('$inv_number', '$inv_number = :n', n='ZZ').fetchRecord(ignoreMissing=True)
# None
invoice.query('$inv_number').fetchRecord()
# RecordMultipleRowsError: Multiple visible records in invc.invoice
invoice.query('$inv_number', order_by='$total DESC').fetchRecord()
# {'inv_number': 'B1'} compiled with LIMIT 1
Scene 05
Counting
count executes a counting version of the query. For an ordinary query it selects count star and returns the number. For a query with group_by it selects the group keys and returns the number of groups. For a query with distinct it keeps the projection and returns the number of distinct rows. ORDER BY is removed. LIMIT and OFFSET are kept, and they apply to the counting query, not to the rows counted. A limit of two on an ordinary query still returns four, because count star produces one row.
Example & screen notes
Slide title "count()". Code:
invoice.query('$id').count() # 4
# SELECT count(*) AS "gnr_row_count" FROM ... WHERE (...)
invoice.query('$customer_id', distinct=True).count() # 2
invoice.query('$customer_id', group_by='$customer_id').count() # 2
Scene 06
Cursors
The examples from here on use a query without a filter, which returns the four invoices. When the result is large, read it from a cursor. execute and cursor both run the query and return the open cursor. The cursor has description, rowcount, fetchone, fetchmany, fetchall and iteration, and its rows are NamedList. You close it. servercursor opens a named server-side cursor on PostgreSQL; on SQLite it returns a normal cursor. iterfetch yields lists of at most arraysize rows, read from a server-side cursor. The default arraysize is thirty. The last list is empty. serverfetch returns the cursor and the same generator. The macros hash BAG and hash BAGCOLS are applied by iterfetch and serverfetch, but not by execute and cursor: a cursor returns the XML text.
Example & screen notes
Slide title "Cursors". Code:
query = invoice.query('$id, $inv_number, $customer_id, $total', order_by='$inv_number')
cursor = query.execute() # the same as query.cursor()
cursor.description[0][0] # 'id'
cursor.fetchone() # a NamedList
cursor.fetchmany(2)
cursor.fetchall()
cursor.close()
[len(chunk) for chunk in query.iterfetch(arraysize=3)] # [3, 1, 0]
cursor, chunks = query.serverfetch(arraysize=2) # chunks yields [2, 2, 0]
Bullets:
- execute() / cursor(): open cursor; the caller closes it
- servercursor(): named server-side cursor on PostgreSQL, normal cursor on SQLite
- iterfetch() / serverfetch(): lists of at most arraysize rows; the last list is empty
Scene 07
Rows in other shapes
Several terminals reshape the rows of fetch. fetchPkeys returns the primary-key values in query order. fetchAsDict returns a dictionary keyed by a column; by default the key is the primary key. With ordered equal True it returns an OrderedDict. With pkeyOnly equal True each value is the primary key instead of the row. fetchGrouped returns lists of rows grouped by a column, in the order of the fetch. Each of these terminals reads the key from the rows. The key column must be in the projection. fetchPkeys on a query without the id column raises KeyError.
Example & screen notes
Slide title "fetchPkeys, fetchAsDict, fetchGrouped". Code:
query.fetchPkeys() # the id values, in query order
query.fetchAsDict(key='inv_number') # {'A1': row, 'A2': row, 'B1': row, 'B2': row}
query.fetchAsDict(key='inv_number', pkeyOnly=True) # {'A1': id, 'A2': id, ...}
query.fetchAsDict(ordered=True) # OrderedDict keyed by id
query.fetchGrouped(key='customer_id') # {customer id: [row, ...]}
Bullets:
- key=None means the primary key
- the key column must be in the projection
Scene 08
JSON and Bag
fetchAsJson returns the rows as a JSON array of objects. Datetimes are written in ISO format. Every other value that JSON does not support goes through str, so a decimal total becomes a string. The method accepts a key argument, but it does not use it. fetchAsBag returns a Bag with one node per row. The node label is the key column, and the row is stored in the node attributes. The nodes are sorted by the key. fetchGrouped with asBag equal True returns the groups in a Bag.
Example & screen notes
Slide title "fetchAsJson, fetchAsBag". Code:
query.fetchAsJson()
# '[{"id": "...", "inv_number": "A1", "customer_id": "...", "total": "50.00"}, ...]'
bag = query.fetchAsBag(key='inv_number')
list(bag.keys()) # ['A1', 'A2', 'B1', 'B2']
bag.get_attr('A1') # the row as attributes
query.fetchGrouped(key='customer_id', asBag=True) # a Bag of lists
Scene 09
Recap
To recap. sqltext, sqlparams, test and compiled inspect a query without running it. fetch returns mutable NamedList rows, and every terminal executes the SQL again. fetchRecord returns one row, count returns a number, and cursors read large results in pieces. The next lesson covers the environment.
Example & screen notes
Slide title "Recap". Bullets:
- sqltext, sqlparams, test(), compiled: inspection without execution
- fetch() returns mutable NamedList rows; every terminal executes again
- fetchRecord() for one row, count() for a number, cursors and iterfetch() for large results
Footer: "Next: lesson 17 — The environment".