Contributing a Recipe
This page explains how the cookbook is organised, and how to add a new recipe or a new language. The conventions on this page keep the language tabs working across the site, and ensure that continuous integration tests every published code sample.
Understanding the Repository Layout
Each recipe consists of a documentation page and a set of runnable programs, one for each language. The following example shows the layout for a single recipe:
docs/
postgresql/
connect_and_query.md # The recipe page.
recipes/
postgresql/
connect_and_query/
expected_output.txt # The output every program must produce.
check_output # Optional; replaces expected_output.txt.
failure_env.txt # Optional; makes the server unreachable.
requires.txt # Optional; extra variables required.
python/ # One complete project per language.
typescript/
java/
csharp/
go/
rust/
ruby/
c/
sql/ # Extension recipes: main.sql only.
scripts/
languages.py # The canonical list of languages.
tabs.py # Generates tab groups for a page.
check_tabs.py # Validates tab groups in the docs.
render_docs.py # Renders the docs for publication.
run_example.sh # Builds and runs one program.
start_test_server.sh # Starts a TLS-enabled test server.
start_rag_server.sh # Starts the pgEdge RAG Server.
start_workbench.sh # Starts the AI DBA Workbench.
fixtures/rag_documents.json # The RAG Server's sample documents.
fixtures/rag_private_documents.json # The row-level security documents.
test_recipes.sh # Tests every program.
The cookbook groups recipes by product; recipes for PostgreSQL itself live
under postgresql/. Recipes for the HTTP APIs of pgEdge products live
under rag_server/ for the pgEdge RAG Server, ai_dba_workbench/ for
the AI DBA Workbench, starfleet/ for the pgEdge Starfleet API,
postgres_mcp/ for the pgEdge Postgres MCP Server, and control_plane/
for the pgEdge Control Plane; the section on HTTP API recipes below
describes them. Recipes for PostgreSQL extensions live under snowflake/
for Snowflake, vectorizer/ for the pgEdge Vectorizer, and
semantic_cache/ for pg_semantic_cache; the section on SQL extension
recipes below describes them.
Recipes for the pgEdge Go LLM Library are the exception to the one-program-per-language rule; the section on single-language recipes below describes their layout.
Using the Canonical Language Labels
Content tabs switch together because they share a label, so a misspelt or missing label breaks the language switcher without any visible error. Every tab group on a recipe page must use the canonical labels, in the canonical order.
The following table lists each label with its directory and source file:
| Label | Directory | Main source file |
|---|---|---|
| Python | python |
main.py |
| TypeScript | typescript |
main.ts |
| Java | java |
src/main/java/Main.java |
| C# | csharp |
Program.cs |
| Go | go |
main.go |
| Rust | rust |
src/main.rs |
| Ruby | ruby |
main.rb |
| C | c |
main.c |
Which set of labels a page uses depends on its product directory, and two rules apply:
- Pages under
docs/rag_server/,docs/ai_dba_workbench/,docs/starfleet/,docs/postgres_mcp/, anddocs/control_plane/document HTTP API recipes, and use every language except C, in the order above. Acurltab may follow them, for a recipe with acurl/main.shscript. The products are listed inAPI_PRODUCTSinscripts/languages.py. - Pages under
docs/snowflake/,docs/vectorizer/, anddocs/semantic_cache/document SQL extension recipes (listed inEXTENSION_PRODUCTS), and have no tab groups at all; the section on SQL extension recipes below describes them. - Every other page uses all eight languages, in the order above.
In both cases a recipe may omit a language by leaving out its directory;
the tab stays on the page, and the author replaces the placeholder
sentence that scripts/tabs.py writes into it, as described in the
section on adding a recipe. The scripts/tabs.py script picks the set
from the recipe's product directory under recipes/, and adds the curl
tab, in every group except deps, when the recipe has a curl/main.sh
file. The scripts/languages.py file defines both sets and the API
product directories, and scripts/check_tabs.py fails the build if any
tab group departs from the set for its page.
Marking Sections in the Code
A recipe page never contains code directly; instead, each tab includes a
named section of a program with the
Snippets
extension. A pair of comments marks each section in the source file; the
following example marks the connect section of a Python program:
# --8<-- [start:connect]
def connect() -> psycopg.Connection:
"""Opens a connection with the settings from the environment."""
return psycopg.connect(**connection_settings())
# --8<-- [end:connect]
The Markdown source of this page escapes the markers above with a leading semicolon, which prevents the extension from treating them as real markers; the extension removes the semicolon when the page is published.
Every program in a recipe uses the same section names, except where a step needs no code in a particular language. The first recipe uses the following sections:
importsfor the driver and library imports.configfor reading the connection settings.connectfor opening the connection.queryfor running the query and reading the result.mainfor calling the other functions, reporting failures, and closing the connection.
Each section must be a single contiguous block, because the extension
includes only the first block when a name appears more than once in a
file. Each section must also be complete code that a reader can copy, so
a section never opens a function, a block, or a try statement that a
later section closes. The programs therefore put each step in its own
function, and the main section holds the error handling and cleanup
that wrap the calls. The scripts/check_tabs.py script rejects a section
whose brackets do not balance, a section that starts with a keyword such
as catch or rescue, and a Python section that does not parse on its
own. The dependency
manifest of each project marks a
deps section in the same way, except for package.json, which cannot
contain comments and so appears in full. XML does not allow two
consecutive hyphens inside a comment, so XML manifests such as pom.xml
and *.csproj files write the scissors with single hyphens, as -8<-;
the extension accepts either form, and scripts/check_tabs.py rejects the
double-hyphen form in XML files. The extension removes the marker
comments from the published page, including when the page shows a whole
file.
Adding a Recipe
The following steps add a new recipe to the cookbook:
- Create a directory under
recipes/for the recipe, with a subdirectory for each language and anexpected_output.txtfile. - Write a complete program in each language that reads its connection
settings from the
PG*environment variables and prints exactly the contents ofexpected_output.txt. - Mark the same named sections in each program, and the
depssection in each dependency manifest. - Run
scripts/test_recipes.shagainst a local PostgreSQL server, and confirm that every program passes. - Generate the tab groups for each section with
scripts/tabs.py, and write the recipe page around them. - Add the page to the
navsection ofmkdocs.ymland to the table on the introduction page. - Build the site with
zensical build, and runscripts/check_tabs.py.
The following command prints the tab groups for the imports and
connect sections of the first recipe:
scripts/tabs.py recipes/postgresql/connect_and_query imports connect
A step that needs no code in a particular language should still keep the tab for that language, with a sentence that explains why; the Closing the Connection step of the first recipe demonstrates this approach.
A recipe may omit a language altogether, by leaving out its directory.
Where the directory is missing, scripts/tabs.py writes a placeholder
sentence into that language's tab, and the author replaces it by hand with
an explanation; scripts/check_tabs.py fails the build while any
placeholder remains. The Using a Connection Pool recipe has no C program
for this reason.
Sharing the Test Server
Recipes run concurrently against one shared server during development, so
a recipe uses a TEMPORARY table wherever a single session suffices. When
a recipe needs a table that several sessions share, the table is named
cookbook_<recipe slug> (for example, cookbook_retry_transactions), and
the program drops the table before it starts and again when it finishes. A
recipe that uses LISTEN names its channel in the same way. Fixture data is
obviously fake, such as Jane Doe and [email protected].
Run only one copy of a recipe at a time against a server. Recipes that share
a cookbook_<slug> table, such as retry_transactions, would race with a
second run of the same recipe, although different recipes can run together.
Testing the Programs
The scripts/test_recipes.sh script builds and runs every program, and
compares the output with the expected_output.txt file for the recipe. The
script then runs each program a second time against a port with no server,
and checks that the program exits with an error. The following commands
test every Python and Go program against a local server:
export PGHOST=localhost PGUSER=postgres PGPASSWORD=postgres
scripts/test_recipes.sh python go
Recipes that need more than a PostgreSQL server, such as recipes for the
pgEdge Starfleet API, list the extra environment variables that they need
in a requires.txt file. The test script skips those recipes when any of
the variables are unset or empty, so the command above tests only the
recipes that a PostgreSQL server is enough for. Continuous integration
passes these variables from repository secrets of the same name, and skips
the recipe when a secret is absent.
The --dir option limits a run to the recipes under one directory, and
the --exclude option skips the recipes under one; both options may be
repeated. The following command tests the RAG Server recipes in every
API language:
scripts/test_recipes.sh --dir recipes/rag_server \
python typescript java csharp go rust ruby curl
A recipe whose output varies from run to run, such as a reply from a
language model, has an executable check_output file in place of
expected_output.txt; a recipe never has both. The test script pipes the
standard output of each program to check_output, and the program passes
when the script exits with status 0. The script checks the shape of the
output, such as a non-empty reply, the expected line prefixes, or
well-formed values, and never the exact text, and it must fail on empty
output and on error messages.
The unreachable-server run sets PGPORT=1 by default. A recipe whose
program does not use the PG* variables, such as one that calls a
language model, replaces that default with a failure_env.txt file of
NAME=value lines, one per line, which the test script exports for that
run. The following example points an LLM recipe at a closed port:
LLM_BASE_URL=http://localhost:1
A curl program takes part in the unreachable-server run only when the
recipe has a failure_env.txt file, because a shell script never reads
the PG* variables through libpq.
When scripts/test_recipes.sh receives SIGINT or SIGTERM, it passes the
signal to the program that is running and waits for the program to finish,
so that a program that deletes what it created can do so.
The scripts/start_test_server.sh script starts the server that
continuous integration uses: the cookbook-test-server image in Docker,
with trust authentication and TLS enabled. The image adds
pg_semantic_cache to the pgEdge Postgres image, which already carries
pgvector, Snowflake, and the pgEdge Vectorizer; the script builds it from
scripts/test_server/Dockerfile the first time it runs, and again after
the Dockerfile changes. The server's vectorizer creates embeddings through
Ollama with nomic-embed-text, at the URL that the --ollama-url option
gives (http://host.docker.internal:11434 by default); on Linux, Ollama
must listen on an address that containers can reach, such as
OLLAMA_HOST=0.0.0.0:11434. The script generates a
certificate authority and a server certificate into a temporary directory
each time it runs, with subject alternative names for localhost,
127.0.0.1, and any host names given as arguments, and it never writes
the keys into the repository. The script prints the path of the root
certificate as PGSSLROOTCERT=<path>; export that variable to run the TLS
recipe, which requires.txt skips otherwise. The following commands start
a server on port 5433, test the Python programs against it, and remove it:
scripts/start_test_server.sh --name cookbook-pg --port 5433
export PGHOST=localhost PGPORT=5433 PGUSER=postgres
scripts/test_recipes.sh python
docker rm -f cookbook-pg
The script accepts --name, --network, --port, and --ollama-url
options, and the COOKBOOK_PG_IMAGE environment variable overrides the
image. If the server fails to start, the script prints the server log,
and removes the container and the certificate directory.
Writing Single-Language Recipes
Recipes for the
pgEdge Go LLM Library show
Go only, because the library is a Go package. These recipes live under
recipes/go_llm_lib/<slug>/go/, with their pages at
docs/go_llm_lib/<slug>.md, and they appear in the navigation under the
group Using the pgEdge Go LLM Library. A single-language page uses plain
fenced code blocks with snippet includes instead of content tabs, so
scripts/check_tabs.py ignores the page without any exemption. The
program still marks its sections as described earlier, and each section
must still be complete, copyable code.
The programs read the following variables, and treat an empty value as unset:
| Variable | Default | Purpose |
|---|---|---|
LLM_PROVIDER |
ollama |
The provider for chat requests. |
LLM_MODEL |
qwen2.5:1.5b |
The chat model. |
LLM_API_KEY |
none | The API key, if one is needed. |
LLM_BASE_URL |
the provider's own | Overrides the provider endpoint. |
EMBED_PROVIDER |
ollama |
The provider for embeddings. |
EMBED_MODEL |
nomic-embed-text |
The embedding model. |
Every recipe defaults to a local Ollama server, so the first recipe
explains how to run the examples with Ollama and the two models. Recipes
that need a cloud API key list the variable in requires.txt.
Writing HTTP API Recipes
Recipes for the pgEdge RAG Server, the AI DBA Workbench API, the pgEdge
Starfleet API, the pgEdge Postgres MCP Server, and the pgEdge Control Plane
API live under recipes/<product>/<slug>/, with their pages at
docs/<product>/<slug>.md, where <product> is rag_server,
ai_dba_workbench, starfleet, postgres_mcp, or control_plane. Each
recipe has a program in every language except C, and a curl/main.sh
script unless its steps cannot be written as HTTP calls. The Python programs
use httpx, the TypeScript programs use the built-in fetch function, the
Java programs use the java.net.http package with Jackson, the C# programs use
HttpClient with System.Text.Json, the Go programs use the net/http
package, the Rust programs use the blocking reqwest client with serde_json,
the Ruby programs use Net::HTTP and json from the standard library, and the
curl scripts use jq to read JSON.
Every page states the product version that its examples target, near the top of the page; the RAG Server recipes target RAG Server 2.0.0, the Workbench recipes target AI DBA Workbench 1.0.0, the Starfleet recipes target the Starfleet Managed API v1, the Postgres MCP Server recipes target pgEdge Postgres MCP Server 1.1.0, and the Control Plane recipes target the Control Plane v0.11.0.
The programs read the following variables, and treat an empty value as unset:
| Variable | Default | Purpose |
|---|---|---|
RAG_SERVER_URL |
http://localhost:8080 |
The RAG Server's base URL. |
RAG_PIPELINE |
cookbook; cookbook_private for row-level security |
The pipeline to query. |
OLLAMA_URL |
http://localhost:11434 |
Ollama, for the data preparation recipe. |
WORKBENCH_URL |
http://localhost:8080 |
The Workbench server's base URL. |
WORKBENCH_TOKEN |
none | A Workbench API token. |
WORKBENCH_USERNAME |
none | The user for the login recipe. |
WORKBENCH_PASSWORD |
none | The password for the login recipe. |
WORKBENCH_CONNECTION_ID |
none | The ID of a monitored connection. |
STARFLEET_CLIENT_ID |
none | The Starfleet API client ID. |
STARFLEET_CLIENT_SECRET |
none | The Starfleet API client secret. |
STARFLEET_API_URL |
https://api.pgedge.com |
The Starfleet API's base URL. |
MCP_URL |
http://localhost:8080 |
The Postgres MCP Server's base URL. |
MCP_TOKEN |
none | An API token that the MCP Server accepts. |
CONTROL_PLANE_URL |
http://localhost:3000 |
The Control Plane's base URL. |
CONTROL_PLANE_DATABASE_ID |
cookbook |
The ID of an existing database. |
CONTROL_PLANE_DATABASE_USER |
cookbook |
The database user to connect as. |
CONTROL_PLANE_DATABASE_PASSWORD |
none | The password of that user. |
CONTROL_PLANE_RECIPE_PORT |
6433 |
The port of the database that the creation recipe adds. |
Each recipe lists the variables that have no default in requires.txt, so
the test script skips the recipe when they are not set. The RAG Server
recipes list RAG_SERVER_URL (and the data preparation recipe lists
OLLAMA_URL) even though both have defaults, so that a test run skips them
unless you have started the service and exported the variable. A
failure_env.txt file points the URL variable at port 1, so that every
program, including the curl script, must fail when the service is
unreachable. A recipe whose output comes from a language model has a
check_output script instead of expected_output.txt.
The scripts/start_rag_server.sh script loads the fictional Acme Widget
documents in scripts/fixtures/rag_documents.json into a
cookbook_rag_documents table on the test server, embedding each one
with the Ollama nomic-embed-text model, and starts the RAG Server 2.0.0
container with a cookbook pipeline that uses Ollama for embeddings and
the qwen2.5:1.5b model for answers. The script prints the
RAG_SERVER_URL and RAG_PIPELINE lines to export.
The scripts/start_workbench.sh script starts AI DBA Workbench 1.0.0 with
the Docker Compose file from the v1.0.0 tag, which it checks against a
pinned SHA-256 before using it, and the published images, using Ollama as
its language model. The script creates a cookbook user
with a generated password and an API token, registers the test server as
a monitored connection, and waits until the collector has stored metrics
for it. The script then prints the WORKBENCH_URL, WORKBENCH_TOKEN,
WORKBENCH_USERNAME, WORKBENCH_PASSWORD, and WORKBENCH_CONNECTION_ID
lines to export; the generated secrets stay in a temporary directory and
never enter the repository.
Both scripts accept --name, --port, --network, --pg-host,
--pg-port, and --ollama-url options, and start_rag_server.sh also
accepts --identity, described below; the --pg-host and --pg-port
options give the test server's address as the containers see it, which
defaults to the Docker host. On Linux, a service on the Docker host is
reachable from the containers only when it listens on an address other
than 127.0.0.1, so start Ollama with OLLAMA_HOST=0.0.0.0:11434 there;
Docker Desktop has no such restriction. The following commands
start the test server and the RAG Server on a shared network, and test
the RAG Server recipes, other than the row-level security recipe:
scripts/start_test_server.sh --network cookbook --port 5433
export PGHOST=localhost PGPORT=5433 PGUSER=postgres
eval "$(scripts/start_rag_server.sh --network cookbook \
--pg-host cookbook-test-pg --pg-port 5432 --port 8081)"
export RAG_SERVER_URL RAG_PIPELINE OLLAMA_URL=http://localhost:11434
scripts/test_recipes.sh --dir recipes/rag_server \
--exclude recipes/rag_server/row_level_security \
python typescript java csharp go rust ruby curl
The row-level security recipe needs a server of its own, because the
setting that makes a server require a caller identity applies to the whole
server. The --identity option starts that server, with a
cookbook_private pipeline over the documents in
scripts/fixtures/rag_private_documents.json, and prints its own
RAG_SERVER_URL and RAG_PIPELINE lines. The following commands start the
server on a second port, and test the recipe against it:
eval "$(scripts/start_rag_server.sh --identity --network cookbook \
--pg-host cookbook-test-pg --pg-port 5432 --port 8082)"
export RAG_SERVER_URL RAG_PIPELINE
scripts/test_recipes.sh --dir recipes/rag_server/row_level_security \
python typescript java csharp go rust ruby curl
The scripts/start_postgres_mcp.sh script starts the pgEdge Postgres MCP
Server 1.1.0 in Docker. The server runs only read-only queries, so the
script first creates a cookbook_mcp schema, which it drops and recreates
each time, holding a products table of made-up products. It embeds each
description with Ollama through the test server's vectorizer, so the
PostgreSQL server must be the one that scripts/start_test_server.sh
starts, and Ollama must have the nomic-embed-text model. The script then
generates an API token, starts the server in HTTP mode with
authentication, and waits until a similarity_search call with the token
succeeds. It prints the MCP_URL and MCP_TOKEN lines to export, and
accepts --name, --port, --network, --pg-host, --pg-port,
--pg-sslmode, and --ollama-url options. The token reaches the container
through its environment, and docker inspect still shows it, so use the
script only for throwaway servers. If the script fails, it removes what it
created; to stop the server afterwards, run docker rm -f with the
container's name, cookbook-postgres-mcp by default. The following
commands start the MCP Server and test the MCP Server recipes:
eval "$(scripts/start_postgres_mcp.sh)"
export MCP_URL MCP_TOKEN
scripts/test_recipes.sh --dir recipes/postgres_mcp \
python typescript java csharp go rust ruby curl
docker rm -f cookbook-postgres-mcp
The scripts/start_control_plane.sh script starts the Control Plane
v0.11.0 in Docker, initialising a Docker swarm first if Docker is not
already in swarm mode, and creates a one-node database with the ID
cookbook and a cookbook user with a generated password. The script
prints the CONTROL_PLANE_URL, CONTROL_PLANE_DATABASE_ID,
CONTROL_PLANE_DATABASE_USER, and CONTROL_PLANE_DATABASE_PASSWORD lines
to export, and accepts --name, --port, --pg-port, --data-dir, and
--timeout options; --timeout sets how many seconds the script waits for
the database, 600 by default. Running it again with --stop and the same
--name, --port, and --data-dir removes only what it created,
including any database whose ID starts with cookbook-recipe-, which is
the prefix that a recipe must give to the databases that it creates. The
following commands start the Control Plane, test the Control Plane
recipes, and remove the Control Plane again:
eval "$(scripts/start_control_plane.sh)"
export CONTROL_PLANE_URL CONTROL_PLANE_DATABASE_ID \
CONTROL_PLANE_DATABASE_USER CONTROL_PLANE_DATABASE_PASSWORD
scripts/test_recipes.sh --dir recipes/control_plane \
python typescript java csharp go rust ruby curl
scripts/start_control_plane.sh --stop
The Starfleet recipes use a real Starfleet account, so a recipe that
creates a billable resource names the resource with a cookbook- prefix
and always deletes the resource again, even when a step fails. Continuous
integration runs the Starfleet recipes only when started by hand and on a
weekly schedule, and skips them when the credentials are not configured.
Because a cancelled or timed-out run stops the programs before they can
clean up, the job ends with a step that deletes every database created
since the job started whose name starts with cookbook, and whose display
name starts with cookbook-, and nothing else. If you run a Starfleet
recipe locally, unset PGSSLROOTCERT first: the libpq library and pgx
treat sslmode=require as verify-ca when it is set, and reject the
certificate of a Starfleet database.
Writing SQL Extension Recipes
Recipes for PostgreSQL extensions are SQL only, because a driver program
would only wrap the SQL and repeat the connection recipes. They live under
recipes/<product>/<slug>/sql/main.sql, with their pages at
docs/<product>/<slug>.md, where <product> is snowflake for
Snowflake, vectorizer for the pgEdge Vectorizer, or semantic_cache for
pg_semantic_cache. The recipe has an expected_output.txt file or an
executable check_output file, as for any other recipe.
Every page states the version of the extension that its examples target, near the top of the page, in a sentence such as "These examples are for Snowflake 2.6.0." The Snowflake recipes target Snowflake 2.6.0, the Vectorizer recipes target pgEdge Vectorizer 1.1, and the semantic cache recipes target pg_semantic_cache 0.1.0-beta4.
The test script runs the program with
psql -X -q -v ON_ERROR_STOP=1 -f main.sql, so the script reads its
connection settings from the PG* variables, and stops with a non-zero
status at the first error. A script that creates objects, such as a
table or a vectorizer queue entry, instead turns ON_ERROR_STOP off with
\set ON_ERROR_STOP off in its settings section, so that a failed step
does not skip the clean-up. It turns ON_ERROR_STOP on again at the start
of its clean-up section, which drops what the script created, and ends by
checking :LAST_ERROR_SQLSTATE and raising an error if any earlier step
failed, so that the script still exits with a non-zero status. The
recipes/vectorizer/enable_vectorization/sql/main.sql script shows the
pattern. The unreachable-server run sets PGPORT=1, as
for the other recipes. The program starts with \pset format unaligned
and \pset tuples_only on, so that its output is plain lines that
expected_output.txt can match, and a waiting loop, such as one that
waits for the Vectorizer's background worker, is a DO block that calls
pg_sleep and raises an error on a timeout. The following command tests
the Snowflake recipes:
scripts/test_recipes.sh sql --dir recipes/snowflake
The section markers in an SQL file are SQL comments that contain the
usual marker, which the Snippets extension recognises after the leading
--. The following example marks the setup section:
-- --8<-- [start:setup]
CREATE TABLE cookbook_demo (id integer);
-- --8<-- [end:setup]
The Markdown source of this page escapes the markers with a semicolon, as
described earlier. The sections follow the same rules as in other
languages: each is one contiguous block of complete statements, and
scripts/check_tabs.py rejects a section whose brackets do not balance.
An SQL-only page has no content tabs. Run scripts/tabs.py with the
recipe directory and the section names, and it prints a plain sql code
block for each section, instead of a tab group:
scripts/tabs.py recipes/snowflake/<slug> setup full
The full section includes the whole file, and deps is not available.
Write an introductory sentence before each block, as on any other page.
The scripts/check_tabs.py script fails the build if an extension page
contains a tab group, or includes a file or a section that does not exist.
Some state is global to the server, such as the semantic cache table and
the Vectorizer's queues. A program that uses shared state tags what it
creates, or names it, with cookbook, and removes only those rows and
objects when it finishes, including when a step fails, so that a recipe
never touches the data of another recipe or of a real workload.
Publishing the Documentation
The primary pgEdge documentation site imports only the docs/ directory of
each source repository, so the site cannot read the programs that the
recipe pages include. The scripts/render_docs.py script produces a
self-contained copy of docs/ and mkdocs.yml, with the code from each
snippet include written directly into the page, and with the Snippets
extension removed from the configuration.
After the tests pass on the main branch, the publish workflow renders the
documentation and replaces the published branch with the result; the
primary site imports the published branch. The published branch holds
generated output only, so authors never edit or merge into the branch.
The following command renders the documentation into build/published,
which a local build can then check:
scripts/render_docs.py
cd build/published && zensical build --strict
Following the Code Conventions
The programs serve as teaching material, so the code favours clarity over brevity. Contributions should follow these conventions:
- Each program is complete and runs without modification.
- Each program passes values to SQL statements as parameters, and never builds SQL from strings.
- Each program reports errors on
stderrand exits with a non-zero status. - Each program treats an empty environment variable as unset, as libpq does, so every language behaves in the same way.
- Comments explain why the code takes an approach, not only what the code does.
- Each project manifest requires a current release of each dependency.
- Each project commits the lock file for toolchains that conventionally
commit one, namely
Cargo.lock,package-lock.json,go.sum, andGemfile.lock.