psycodict.database¶
The connection object at the center of psycodict.
PostgresDatabase connects using a
Configuration and exposes each table in the
database as an attribute (db.nf_fields), a
PostgresSearchTable. It also provides the
database-wide operations: creating, dropping and renaming tables,
reloading or cleaning up every table at once, schema-change notifications
(listener()), refreshing table metadata without a
restart (refresh_tables()), and the metadata
format stamp and its migrations.
- class psycodict.database.NumericLoader(oid: int, context: AdaptContext | None = None)[source]¶
Bases:
LoaderLoads Postgres numeric values through
numeric_converter()(Sage Integer/RealLiteral when Sage is available, int/float otherwise).
- psycodict.database.setup_connection(conn)[source]¶
Prepare a fresh psycopg connection for psycodict: set the client encoding and register the loaders and dumpers that implement psycodict’s value conversion (numerics, json, arrays, and – when Sage is available – Sage integers and reals). Called for every connection the database opens.
- class psycodict.database.PostgresDatabase(config=None, secretsfile=None, create=False, upgrade=False, **kwargs)[source]¶
Bases:
PostgresBaseThe interface to the postgres database.
It creates and stores the global connection object, and collects the table interfaces.
A single psycopg connection is shared by this database object and every table interface registered on it (see
register_objectandreset_connection). The psycopg connection itself is thread-safe – it serializes concurrent cursor use with an internal lock – but sharing one connection means sharing one transaction and session state, and psycodict layers unsynchronized mutable bookkeeping on top (the commit-deferral stack behindDelayCommit, the server-side cursor counter, the per-object connection references reset together). So aPostgresDatabaseinstance is not thread-safe; use one instance per process or thread (this is how LMFDB deploys it).INPUT:
create– if True, create psycodict’s metadata tables (meta_tables, meta_indexes, meta_constraints and their _hist counterparts) when they are missing, allowing use of a fresh databaseupgrade– if True, migrate the metadata tables to the format this psycodict implements before connecting (see upgrade_metadata and MetadataFormats.md). Without it, a database using an older but compatible metadata format connects with a warning and operates at the older format.**kwargs– passed on to psycopg’s connect method
ATTRIBUTES:
The following public attributes are stored on the db object.
server_side_counter– an integer tracking how many buffered connections have been createdconn– the psycopg connection objecttablenames– a list of tablenames in the database, as stringsmeta_format– the metadata format this connection operates at (see MetadataFormats.md)
Also, each tablename will be stored as an attribute, so that db.ec_curvedata works for example.
These table objects are snapshots: if another process later changes the schema (adding or dropping columns or tables), call
refresh_tablesto update them in place instead of restarting the process.EXAMPLES:
>>> db Interface to Postgres database >>> db.conn <psycopg.Connection [IDLE] (host=... database=...) at 0x...> >>> 'test_fields' in db.tablenames True >>> db.test_fields Interface to Postgres table test_fields
- refresh_tables()[source]¶
Update the table objects to match the current state of the database.
The set of tables, and each table’s columns, types, sort order and other metadata, are read from the database when this object is created. A long-running process (such as a website) therefore does not see schema changes made from other processes: after a column is dropped, for example, its queries still mention the column and fail, while a newly added column is silently invisible. Rather than restarting every such process, call this method to bring the process up to date – on a schedule, say, or upon catching an
errors.UndefinedColumn.Existing table objects are updated in place, so table references held by application code (
nf = db.nf_fields) remain valid. Tables created since the last refresh become accessible as attributes and are added totablenames; tables that have been dropped are removed. A reference held to a dropped table’s object will raise an error on its next query, as it must, since the underlying table no longer exists.
- grant_select(table_name, users=['lmfdb', 'webserver'])[source]¶
Grant users the ability to run SELECT statements on a given table
INPUT:
table_name– a string, the name of the tableusers– a list of users to grant this permission
- grant_insert(table_name, users=['webserver'])[source]¶
Grant users the ability to run INSERT statements on a given table
INPUT:
table_name– a string, the name of the tableusers– a list of users to grant this permission
- grant_update(table_name, users=['webserver'])[source]¶
Grant users the ability to run UPDATE statements on a given table
INPUT:
table_name– a string, the name of the tableusers– a list of users to grant this permission
- grant_delete(table_name, users=['webserver'])[source]¶
Grant users the ability to run DELETE statements on a given table
INPUT:
table_name– a string, the name of the tableusers– a list of users to grant this permission
- table_sizes()[source]¶
Returns a dictionary containing information on the sizes of the search tables.
OUTPUT:
A dictionary with a row for each search table (as well as a few others such as kwl_knowls), with entries
nrows– an estimate for the number of rows in the tablenstats– an estimate for the number of rows in the stats tablencounts– an estimate for the number of rows in the counts tabletotal_bytes– the total number of bytes used by the main table, as well as stats, counts, indexes, ancillary storage….index_bytes– the number of bytes used for indexes on the main tabletoast_bytes– the number of bytes used for storage of variable length data types, such as strings and jsonbtable_bytes– the number of bytes used for fixed length storage on the main tablecounts_bytes– the number of bytes used by the counts tablestats_bytes– the number of bytes used by the stats table
- property meta_format¶
The metadata format this connection operates at.
This is the format stamped in the database, capped at the format this psycodict implements (
META_FORMAT). When it is lower thanMETA_FORMAT, features introduced by newer formats are unavailable (their methods raise with instructions) until the database is migrated withupgrade_metadata(); see MetadataFormats.md.
- upgrade_metadata()[source]¶
Migrate this database’s metadata tables up to the format that this version of psycodict implements (
META_FORMAT), applying each registered migration in order and stamping the format as it goes.Connecting to an older-format database only warns (when the formats are compatible; see MetadataFormats.md), so migrating is a deliberate act – this method, or
PostgresDatabase(config=..., upgrade=True), which bootstraps (whencreate=True), migrates, and then connects, all in one call.Migrations only move forward. A database already at the current format is left untouched, so calling this is idempotent; a database whose format is newer than this psycodict is an error (upgrade psycodict instead), as is a
meta_formattable that exists but is empty (its format is unknown, so it cannot be migrated blindly).
- create_table_like(new_name, table, tablespace=None, data=False, indexes=False)[source]¶
Creates a new table with the same schema as an existing one, including each column’s storage and compression settings. By default neither data, indexes nor stats are copied.
INPUT:
new_name– a string giving the desired table name.table– a string or PostgresSearchTable object giving an existing table.tablespace– the tablespace for the new tabledata– whether to copy over data from the source tableindexes– whether to copy over indexes from the source table
- create_table(name, search_columns, label_col, table_description=None, col_description=None, sort=None, id_ordered=None, tablespace=None, force_description=False, id_type='bigint', include_nones=True)[source]¶
Add a new search table to the database. See also
create_table_like().INPUT:
name– the name of the table, which must include an underscore. See existing names for consistency.search_columns– either a dictionary whose keys are valid postgres types and whose valuesare lists of column names (or just a string if only one column has the specified type); or a list of pairs (col, type). An id column of type
id_typewill be added as a primary key if not present.
label_col– the column holding the LMFDB label. This will be used in thelookupmethodand in the display of results on the API. Use None if there is no appropriate column.
table_description– a text description of this tablecol_description– a dictionary giving descriptions for the columnssort– If not None, provides a default sort order for the table, in formats accepted bythe
_sort_strmethod.
id_ordered– boolean (default None). If set, the table will be sorted by id whenpushed to production, speeding up some kinds of search queries. Defaults to True when sort is not None.
tablespace– (optional) a postgres tablespace to use for the new tableforce_description– whether to require descriptionsid_type– what postgres type to use for the id columninclude_nones– whether search results should include columns whose value is None (default True). Pass False to omit None values from result dictionaries, as was the default before psycodict 1.0. The value is stored explicitly in meta_tables, so the flipped default reaches databases created before it without any migration.
COMMON TYPES:
The postgres types most commonly used are:
smallint – a 2-byte signed integer.
integer – a 4-byte signed integer.
bigint – an 8-byte signed integer.
numeric – exact, high precision integer or decimal.
real – a 4-byte float.
double precision – an 8-byte float.
text – string (see collation note above).
boolean – true or false.
jsonb – data iteratively built from numerics, strings, booleans, nulls, lists and dictionaries.
timestamp – 8-byte date and time with no timezone.
- drop_table(name, force=False)[source]¶
Drop a table.
INPUT:
name– the name of the tableforce– refrain from asking for confirmation
NOTE:
You cannot drop a table that has been marked important. You must first set it as not important if you want to drop it.
- rename_table(old_name, new_name)[source]¶
Rename a table.
INPUT:
old_name– the current name of the table, as a stringnew_name– the new name of the table, as a string
- copy_to(search_tables, data_folder, fail_on_error=True, **kwds)[source]¶
Copy a set of search tables to a folder on the disk.
INPUT:
search_tables– a list of strings giving names of tables to copydata_folder– a path to a folder to save the data. The folder must not currently exist.**kwds– other arguments are passed on to thecopy_tomethod of each table.
- copy_to_from_remote(search_tables, data_folder, remote_opts=None, fail_on_error=True, **kwds)[source]¶
Copy data to a folder from a postgres instance on another server.
INPUT:
search_tables– a list of strings giving names of tables to copydata_folder– a path to a folder to save the data. The folder must not currently exist.remote_opts– options for the remote connection (passed on to psycopg’s connect method)**kwds– other arguments are passed on to thecopy_tomethod of each table.
- reload_all(data_folder, halt_on_errors=True, resort=None, restat=None, adjust_schema=False, sequential_swap=False, **kwds)[source]¶
Reloads all tables from files in a given folder. The filenames must match the names of the tables, with
_countsand_statsappended as appropriate.INPUT:
data_folder– the folder that contains files to be reloadedhalt_on_errors– whether to stop if a DatabaseError is encountered while trying to reload one of the tablessequential_swap– if True, then the whole transaction will not be wrapped in a DelayCommit, which can sometimes prevent deadlocksresort,restat,adjust_schema, and any extra keywords are passed on to thereloadmethod of eachPostgresTable
Note that this function currently does not reload data that is not in a search table, such as knowls or user data.
- reload_all_revert(data_folder)[source]¶
Reverts the most recent
reload_allby swapping with the backup table for each search table modified.INPUT:
data_folder– the folder used inreload_all;determines which tables were modified.
- show_queries()[source]¶
Prints the queries currently running in this database (which may be holding the locks shown by
show_locks; seeshow_blockedfor statements that are stuck behind them).
- show_blocked()[source]¶
Prints the statements that are waiting on locks held by other sessions, together with the sessions holding them.
- compare(other, tables=None, row_counts=True, null_counts=False, exact=False)[source]¶
Returns the differences between the search tables of this database and those of another one, such as a beta and a production server.
The comparison is read-only on both sides: only SELECT statements are issued, and (unlike the
countmethod of a table, which may cache its results when count saving is enabled) nothing is recorded in the counts tables or meta_tables of either database, soothermay safely be a production server.INPUT:
other– anotherPostgresDatabaseinstance; you connect to the second server yourself (see the examples below)tables– a table name or list of table names (default None, meaning every search table of either database); restricts every part of the comparison to those tablesrow_counts– boolean (default True). Whether to compare the number of rows in tables present in both databasesnull_counts– boolean (default False). Whether to compare the number of NULL entries in each column shared by both sides. This requires a full scan of each compared table in each database, which can take minutes for the largest LMFDB tables, so consider restrictingtablesexact– boolean (default False). By default row counts are read from the total cached in meta_tables, which psycodict’s write paths maintain andcount()reports; that is a single cheap query per database, but it can be stale if a table was modified from outside psycodict or its statistics were never refreshed. Set to True to run SELECT COUNT(*) on each compared table instead, which is exact but slow on big tables (the counting scans run with whatever statement timeout each connection has)
OUTPUT:
A dictionary with keys
only_in_self,only_in_other– sorted lists of the names of search tables present in only one of the databasesschema– a dictionary indexed by the tables present in both databases whose schemas differ, with values dictionaries containing whichever of the following differences occur:only_in_selfandonly_in_other(lists of pairs(col, type)of columns present on one side only),type_changed(a list of triples(col, type_self, type_other), with types rendered in full so that e.g.numeric(10,2)vsnumeric(20,4)is reported) andmeta_changed(a list of triples(item, value_self, value_other)recording disagreements in the label_col, sort, id_ordered, include_nones or count_cutoff settings from meta_tables)row_counts(if requested) –{table: (rows_self, rows_other)}, only for the tables where the counts differnull_counts(if requested) –{table: {col: (nulls_self, nulls_other)}}, only for the columns where the counts differ
EXAMPLES:
Comparing with a server described by a second configuration file:
>>> from psycodict.config import Configuration >>> from psycodict.database import PostgresDatabase >>> prod_config = Configuration(defaults={"config_file": "prod-config.ini"}, readargs=False) >>> prod = PostgresDatabase(config=prod_config) >>> db.compare(prod)["only_in_self"] ['mf_newspaces_test']
Keyword arguments override the configuration, so a server that differs only in its host can reuse this database’s configuration:
>>> prod = PostgresDatabase(config=db.config, host="proddb.lmfdb.xyz") >>> db.compare(prod, tables="mf_newspaces", null_counts=True) {'only_in_self': [], 'only_in_other': [], 'schema': {}, 'row_counts': {}, 'null_counts': {}}
- show_differences(other, tables=None, row_counts=True, null_counts=False, exact=False)[source]¶
Prints a readable report of the differences between the search tables of this database and those of another one.
Sections without differences are omitted; if the databases agree, prints “No differences found”. See
comparefor the meaning of the arguments, the cost of the optional comparisons, and the guarantee that both databases are only read from.
- show_slow_report(logfile, top=20, cutoff=None)[source]¶
Prints an analysis of a slow-query log file: which query shapes take the most time, how much smaller the log would be with a higher
slowcutoff, and which constrained columns lack a supporting index (checked against the indexes recorded inmeta_indexes).See
psycodict.slowlogfor the underlying functions, which can also be used without a database connection.INPUT:
logfile– the filename of a log written via theslowlogfilelogging optiontop– the number of query shapes to showcutoff– only consider queries at least this slow, in seconds
- notify(channel, payload='')[source]¶
Send a PostgreSQL notification on
channelwith the given payload.The notification is sent with
pg_notifyon the main connection, through_execute, so it is transactional: PostgreSQL delivers it when the surrounding transaction commits and drops it on rollback. Called on its own (outside aDelayCommit) it commits immediately and so is delivered at once; called inside aDelayCommitit rides that transaction and is delivered (or dropped) with it.INPUT:
channel– the channel name; must be a plain identifier (letters, digits and underscores, not starting with a digit)payload– a string payload (default""); received verbatim by listeners
Subscribe with
listener()(or the standaloneNotificationListener).
- listener(channels=None)[source]¶
Return a
NotificationListener.The listener opens its own
autocommitconnection from this database’s configuration andLISTEN``s on ``channels(default: the schema channel"psycodict_schema"alone). It is pull-based: callpoll(timeout)for a bounded batch, or iteratelisten(); use it as a context manager to close the connection when done.The intended follow-up use is a long-running website process that keeps a listener on
"psycodict_schema"and, whenever a table name arrives, refreshes that table’s cached metadata so newly created columns and reloaded tables become visible without a restart. That refresh mechanism is proposed in a separate PR; psycodict ships the notification plumbing here without depending on it, so the two can land in either order.Under a pre-forking web server each worker must build its own listener after the fork, and a server in recovery (a hot standby) refuses
LISTENoutright; see the Forking and Hot standbys sections ofpsycodict.notifications.INPUT:
channels– a channel name or iterable of them;None(default) means the schema channel only