diff --git a/.github/workflows/dev_pr/title_check.py b/.github/workflows/dev_pr/title_check.py
index 9297b96f69..78e89c362d 100644
--- a/.github/workflows/dev_pr/title_check.py
+++ b/.github/workflows/dev_pr/title_check.py
@@ -63,25 +63,26 @@ def matches_commit_format(root: Path, title: str) -> typing.List[str]:
reasons.append(f"Invalid commit type: {commit_type}")
components = m.group(2)
- if not components.strip():
- reasons.append("Invalid components: must not be empty")
-
- components = components.split(",")
- for component in components:
- if component != component.strip():
- reasons.append(
- f"Invalid component: must have no trailing space: {component}"
- )
- elif not valid_component.match(component):
- reasons.append(
- "Invalid component: must be alphanumeric with "
- f"dashes, slashes, and underscores: {component}"
- )
- elif component != "format" and not Path(component).exists():
- reasons.append(
- "Invalid component: must reference a file "
- f"or directory in the repo: {component}"
- )
+ if components is not None:
+ if not components.strip():
+ reasons.append("Invalid components: must not be empty")
+
+ components = components.split(",")
+ for component in components:
+ if component != component.strip():
+ reasons.append(
+ f"Invalid component: must have no trailing space: {component}"
+ )
+ elif not valid_component.match(component):
+ reasons.append(
+ "Invalid component: must be alphanumeric with "
+ f"dashes, slashes, and underscores: {component}"
+ )
+ elif component != "format" and not Path(component).exists():
+ reasons.append(
+ "Invalid component: must reference a file "
+ f"or directory in the repo: {component}"
+ )
subject = m.group(3)
if subject.strip() != subject:
diff --git a/docs/mermaid.makefile b/docs/mermaid.makefile
index 9b46cf35e1..cf967f5d7a 100644
--- a/docs/mermaid.makefile
+++ b/docs/mermaid.makefile
@@ -18,6 +18,9 @@
# Generate Mermaid diagrams statically. Sphinx has a mermaid
# extension, but this causes issues with the page shifting during
# load.
+# First: npm install -g @mermaid-js/mermaid-cli
+# (if you are using Conda, this will not be "global" but rather install to
+# your Conda prefix)
# Use as: make -f mermaid.makefile -j all
MERMAID := $(shell find source/ -type f -name '*.mmd')
@@ -27,7 +30,10 @@ define LICENSE
endef
%.mmd.svg : %.mmd
- mmdc --input $< --output $@
+# XXX: mermaid doesn't properly handle comments in all layouts (the parser is
+# written entirely from scratch each time, it looks like), so strip them
+# manually
+ grep -E -v "^%" $< | mmdc --input - --output $@
# Prepend the license header
mv $@ $@.tmp
echo " SqliteStatementExecuteQuery
diff --git a/docs/source/format/DriverAlias.mmd.svg b/docs/source/format/DriverAlias.mmd.svg
new file mode 100644
index 0000000000..5c270b121d
--- /dev/null
+++ b/docs/source/format/DriverAlias.mmd.svg
@@ -0,0 +1,19 @@
+
+
diff --git a/docs/source/format/DriverDirectLink.mmd b/docs/source/format/DriverDirectLink.mmd
new file mode 100644
index 0000000000..7a9974fab0
--- /dev/null
+++ b/docs/source/format/DriverDirectLink.mmd
@@ -0,0 +1,29 @@
+%% Licensed to the Apache Software Foundation (ASF) under one
+%% or more contributor license agreements. See the NOTICE file
+%% distributed with this work for additional information
+%% regarding copyright ownership. The ASF licenses this file
+%% to you under the Apache License, Version 2.0 (the
+%% "License"); you may not use this file except in compliance
+%% with the License. You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing,
+%% software distributed under the License is distributed on an
+%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%% KIND, either express or implied. See the License for the
+%% specific language governing permissions and limitations
+%% under the License.
+
+block-beta
+ columns 3
+ app["Application"]:3
+
+ space:3
+
+ driver["Driver"]:3
+ AdbcStatementExecuteQuery
+ AdbcStatementSetSqlQuery
+ ...
+
+ app --> AdbcStatementExecuteQuery
diff --git a/docs/source/format/DriverDirectLink.mmd.svg b/docs/source/format/DriverDirectLink.mmd.svg
new file mode 100644
index 0000000000..c5471a0229
--- /dev/null
+++ b/docs/source/format/DriverDirectLink.mmd.svg
@@ -0,0 +1,19 @@
+
+
diff --git a/docs/source/format/DriverManagerUse.mmd b/docs/source/format/DriverManagerUse.mmd
new file mode 100644
index 0000000000..c0abc7adc3
--- /dev/null
+++ b/docs/source/format/DriverManagerUse.mmd
@@ -0,0 +1,53 @@
+%% Licensed to the Apache Software Foundation (ASF) under one
+%% or more contributor license agreements. See the NOTICE file
+%% distributed with this work for additional information
+%% regarding copyright ownership. The ASF licenses this file
+%% to you under the Apache License, Version 2.0 (the
+%% "License"); you may not use this file except in compliance
+%% with the License. You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing,
+%% software distributed under the License is distributed on an
+%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%% KIND, either express or implied. See the License for the
+%% specific language governing permissions and limitations
+%% under the License.
+
+block-beta
+ columns 3
+ app["Application"]:3
+
+ space:3
+
+ drivermanager["ADBC Driver Manager"]:3
+
+ space
+ dm_execute_query["AdbcStatementExecuteQuery"]
+ dm_ellipsis["..."]
+
+ space:3
+
+ space
+
+ AdbcDriver["struct AdbcDriver"]:2
+
+ space
+
+ execute_query
+ ellipsis["..."]
+
+ space:3
+
+ driver["Driver"]:3
+ AdbcDriverInit
+ AdbcStatementExecuteQuery
+ ...
+ space
+ SqliteStatementExecuteQuery
+ ellipsis2["..."]
+
+ app --> dm_execute_query
+ dm_execute_query --> execute_query
+ execute_query --> SqliteStatementExecuteQuery
diff --git a/docs/source/format/DriverManagerUse.mmd.svg b/docs/source/format/DriverManagerUse.mmd.svg
new file mode 100644
index 0000000000..c658cf2b18
--- /dev/null
+++ b/docs/source/format/DriverManagerUse.mmd.svg
@@ -0,0 +1,19 @@
+
+
diff --git a/docs/source/format/DriverTableLoad.mmd b/docs/source/format/DriverTableLoad.mmd
new file mode 100644
index 0000000000..44f2d4130b
--- /dev/null
+++ b/docs/source/format/DriverTableLoad.mmd
@@ -0,0 +1,46 @@
+%% Licensed to the Apache Software Foundation (ASF) under one
+%% or more contributor license agreements. See the NOTICE file
+%% distributed with this work for additional information
+%% regarding copyright ownership. The ASF licenses this file
+%% to you under the Apache License, Version 2.0 (the
+%% "License"); you may not use this file except in compliance
+%% with the License. You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing,
+%% software distributed under the License is distributed on an
+%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%% KIND, either express or implied. See the License for the
+%% specific language governing permissions and limitations
+%% under the License.
+
+block-beta
+ columns 3
+ app["Application"]:3
+
+ space:3
+
+ space
+
+ AdbcDriver["struct AdbcDriver"]:2
+
+ space
+
+ execute_query
+ ellipsis["..."]
+
+ space:3
+
+ driver["Driver"]:3
+ AdbcDriverInit
+ AdbcStatementExecuteQuery
+ ...
+ space
+ SqliteStatementExecuteQuery
+ ellipsis2["..."]
+
+ app --> AdbcDriverInit
+ AdbcDriverInit --> AdbcDriver
+ SqliteStatementExecuteQuery --> execute_query
+ ellipsis2 --> ellipsis
diff --git a/docs/source/format/DriverTableLoad.mmd.svg b/docs/source/format/DriverTableLoad.mmd.svg
new file mode 100644
index 0000000000..2b762f68e9
--- /dev/null
+++ b/docs/source/format/DriverTableLoad.mmd.svg
@@ -0,0 +1,19 @@
+
+
diff --git a/docs/source/format/DriverTableUse.mmd b/docs/source/format/DriverTableUse.mmd
new file mode 100644
index 0000000000..84717b6ab3
--- /dev/null
+++ b/docs/source/format/DriverTableUse.mmd
@@ -0,0 +1,45 @@
+%% Licensed to the Apache Software Foundation (ASF) under one
+%% or more contributor license agreements. See the NOTICE file
+%% distributed with this work for additional information
+%% regarding copyright ownership. The ASF licenses this file
+%% to you under the Apache License, Version 2.0 (the
+%% "License"); you may not use this file except in compliance
+%% with the License. You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing,
+%% software distributed under the License is distributed on an
+%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%% KIND, either express or implied. See the License for the
+%% specific language governing permissions and limitations
+%% under the License.
+
+block-beta
+ columns 3
+ app["Application"]:3
+
+ space:3
+
+ space
+
+ AdbcDriver["struct AdbcDriver"]:2
+
+ space
+
+ execute_query
+ ellipsis["..."]
+
+ space:3
+
+ driver["Driver"]:3
+ AdbcDriverInit
+ AdbcStatementExecuteQuery
+ ...
+ space
+ SqliteStatementExecuteQuery
+ ellipsis2["..."]
+
+ app --> execute_query
+
+ execute_query --> SqliteStatementExecuteQuery
diff --git a/docs/source/format/DriverTableUse.mmd.svg b/docs/source/format/DriverTableUse.mmd.svg
new file mode 100644
index 0000000000..6521dfe93b
--- /dev/null
+++ b/docs/source/format/DriverTableUse.mmd.svg
@@ -0,0 +1,19 @@
+
+
diff --git a/docs/source/format/how_manager.rst b/docs/source/format/how_manager.rst
new file mode 100644
index 0000000000..da863e3153
--- /dev/null
+++ b/docs/source/format/how_manager.rst
@@ -0,0 +1,204 @@
+.. Licensed to the Apache Software Foundation (ASF) under one
+.. or more contributor license agreements. See the NOTICE file
+.. distributed with this work for additional information
+.. regarding copyright ownership. The ASF licenses this file
+.. to you under the Apache License, Version 2.0 (the
+.. "License"); you may not use this file except in compliance
+.. with the License. You may obtain a copy of the License at
+..
+.. http://www.apache.org/licenses/LICENSE-2.0
+..
+.. Unless required by applicable law or agreed to in writing,
+.. software distributed under the License is distributed on an
+.. "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+.. KIND, either express or implied. See the License for the
+.. specific language governing permissions and limitations
+.. under the License.
+
+================================================
+How Drivers and the Driver Manager Work Together
+================================================
+
+.. note:: This document focuses on drivers/applications using the C API
+ definitions in adbc.h. That means C/C++/Python/Ruby, and possibly
+ C#/Go.
+
+When an application calls a function like
+:cpp:func:`AdbcStatementExecuteQuery`, how does it "know" what function in
+which driver to actually call?
+
+This can happen in a few ways. In the simplest case, the application links to
+a single driver, and directly calls ADBC functions explicitly defined by the
+driver:
+
+.. figure:: DriverDirectLink.mmd.svg
+
+ In the simplest case, an application directly links to the driver and calls
+ ADBC functions.
+
+This doesn't work with multiple drivers, or applications that don't/can't link
+directly to drivers (think dynamic loading, perhaps in a language like
+Python). For this case, ADBC provides a table of function pointers
+(:cpp:struct:`AdbcDriver`), and a way to request this table from a driver.
+Then, the application proceeds in two steps. First, it dynamically loads a
+driver and calls an entrypoint function to get the function table:
+
+.. figure:: DriverTableLoad.mmd.svg
+
+ Now, the application asks the driver for a table of functions to call.
+
+Then, the application uses the driver by calling the functions in the table:
+
+.. figure:: DriverTableUse.mmd.svg
+
+ The application uses the table to call driver functions. This approach
+ scales to multiple drivers.
+
+Dealing with the table, however, is messy. So the overall recommended
+approach is to use the ADBC driver manager. This is a library that pretends
+to be a single driver that can be linked to and used "like normal".
+Internally, it loads the table of function pointers and tracks which
+database/connection/statement objects need which "actual" driver, making it
+easy to dynamically load drivers at runtime and use multiple drivers from the
+same application:
+
+.. figure:: DriverManagerUse.mmd.svg
+
+ The application uses driver manager to "feel like" it's just using a single
+ driver. The driver manager handles the details behind the scenes.
+
+In More Detail
+==============
+
+The adbc.h header ties everything together. It is the abstract API
+definition, akin to interface/trait/protocol definitions in other languages.
+C being C, however, all it consists of is a bunch of function prototypes and
+struct definitions without any implementation.
+
+A driver, at its core, is just a library that implements those function
+prototypes in adbc.h. Those functions may be implemented in C, or they can be
+implemented in a different language and exported through language-specific FFI
+mechanisms. For example, the Go and C# implementations of ADBC can both
+export drivers to consumers who expect the C API definitions. As long as the
+definitions in adbc.h are implemented somehow, then the application is
+generally none the wiser when it comes to what's actually underneath.
+
+How does an application call these functions, though? Here, there are several
+options.
+
+Again, the simplest case is as follows: if (1) the application links directly
+to the driver, and (2) the driver exposes the ADBC functions *under the same
+name* as in adbc.h, then the application can just ``#include `` and
+call ``AdbcStatementExecuteQuery(...)`` directly. Here, the application and
+driver have a relationship no different than any other C library.
+
+.. figure:: DriverDirectLink.mmd.svg
+
+ In the simplest case, an application directly links to the driver and calls
+ ADBC functions. When the application calls ``StatementExecuteQuery``, that
+ is directly provided by the driver that it links against.
+
+Unfortunately, this doesn't work as well in other scenarios. For example, if
+an application wishes to use multiple ADBC drivers, this no longer works: both
+drivers define the same functions (the ones in adbc.h), and when the
+application links both of them, the linker has no way of telling which
+driver's function is meant when the application calls an ADBC function. On
+top of that, this violates the `One Definition Rule`_.
+
+In this case, the driver can provide driver-specific aliases that applications
+can use, say ``PostgresqlStatementExecuteQuery`` or
+``FlightSqlStatementExecuteQuery``. Then, the application can link both
+drivers, ignore the ``Adbc…`` functions (and ignore the technical violation of
+the One Definition Rule there), and use the aliases instead.
+
+.. figure:: DriverAlias.mmd.svg
+
+ To get around the One Definition Rule, we can provide aliases of the ADBC
+ APIs instead.
+
+This is rather inconvenient for the application, though. Additionally, this
+sort of defeats the point of using ADBC, since now the application has a
+separate API for each driver, even if they're technically all clones of the
+same API. And this doesn't solve the problem for applications that want to
+load drivers dynamically. For example, a Python script would want to load the
+driver at runtime. In that case, it would need to know which functions from
+the driver correspond to which functions in the ADBC API definitions, without
+having to hardcode this knowledge.
+
+ADBC anticipated this, and defined :cpp:struct:`AdbcDriver`. This is just a
+table of function pointers with one entry per ADBC function. That way, an
+application can dynamically load a driver and call an entrypoint function that
+returns this table of function pointers. (It does have to hardcode or guess
+the name of the entrypoint; the ADBC spec lists a set of names it can try,
+based on the name of the driver library itself.)
+
+.. figure:: DriverTableLoad.mmd.svg
+
+ The application first loads a table of function pointers from the driver.
+
+Then, it can use the driver by calling functions in that table:
+
+.. figure:: DriverTableUse.mmd.svg
+
+ The application uses the table to call driver functions. This approach
+ scales to multiple drivers.
+
+Of course, calling all functions by jumping through a giant table of function
+pointers is inconvenient. So ADBC provides the "driver manager", a library
+that _pretends_ to be a simple driver and implements all the ADBC functions.
+Internally, it loads drivers dynamically, requests the tables of function
+pointers, and keeps track of which connections are using which drivers. The
+application only needs to call the standard ADBC functions, just like in the
+simplest case we started out with:
+
+.. figure:: DriverManagerUse.mmd.svg
+
+ The application uses driver manager to "feel like" it's just using a single
+ driver. The driver manager handles the details behind the scenes.
+
+So to recap, a driver should implement these three things:
+
+#. An implementation of each ADBC function,
+#. A thin wrapper around each implementation function that exports the ADBC
+ name for each function, and
+#. An entrypoint function that returns a :cpp:struct:`AdbcDriver` table,
+ containing the functions from (1).
+
+Then, an application has these choices of ways to use a driver:
+
+- Link the driver directly and call ``Adbc…`` functions (only in the simplest
+ cases) using (2) above,
+- Link the driver directly/dynamically, load the :cpp:struct:`AdbcDriver`
+ via (3) above, and call ADBC functions through function pointers (generally
+ not recommended),
+- Link the ADBC driver manager, call ``Adbc…`` functions, and let the driver
+ manager deal with (3) above (what most applications will want to do).
+
+In other words, it's usually easiest to just always use the driver manager.
+But the magic it pulls isn't required or all that complex.
+
+.. note:: You may ask: when we have :cpp:struct:`AdbcDriver`, why do we bother
+ defining both ``AdbcStatementExecuteQuery`` and
+ ``SqliteStatementExecuteQuery`` (i.e., why do both (1) and (2)
+ above)? Can't we just define the ``Adbc…`` version, and put it into
+ the function table when requested?
+
+ Here, implementation constraints come in. At runtime, when the
+ driver looks up the address of (say) ``AdbcStatementExecuteQuery``
+ to put it into the table, the dynamic linker will come into play to
+ figure out where this function is. Unfortunately, it will probably
+ find it *in the driver manager*. This is a problem, since then the
+ driver manager will end up in an infinite loop when it goes to call
+ the "driver's" version of the function!
+
+ By having a seemingly redundant copy of the function, we can then
+ hide the "real implementation" from the dynamic linker and avoid
+ this behavior.
+
+ The driver manager could try to solve this by loading the drivers
+ with ``RTLD_DEEPBIND``. This, however, is not portable, and causes
+ problems if we also want to use things like AddressSanitizer during
+ development. The driver could also build with flags like
+ ``-Bsymbolic-functions``.
+
+.. _One Definition Rule: https://en.cppreference.com/w/cpp/language/definition#One_Definition_Rule
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 494ccfc711..e29d26c482 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -244,6 +244,7 @@ Why ADBC?
format/specification
format/versioning
format/comparison
+ format/how_manager
.. toctree::
:maxdepth: 1