| <!--- |
| 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. |
| --> |
| |
| (user_guide_extensions)= |
| |
| # Using extension libraries |
| |
| An extension library is a separate package that teaches a |
| {py:class}`~datafusion.SessionContext` something it does not know on its own — |
| a new data source, extra functions, or a different way of executing your |
| queries. You install it with `pip`, hand it to your session, and keep writing |
| the same SQL and DataFrame code. |
| |
| Examples in the wild include [delta-rs](https://delta-io.github.io/delta-rs/), |
| which exposes Delta Lake tables to DataFusion, and the two worked examples in |
| this repository under |
| [`examples/`](https://github.com/apache/datafusion-python/tree/main/examples). |
| |
| ## Two kinds of extension |
| |
| Which one you have determines how much setup you do. |
| |
| **Tables and functions register directly.** If the library gives you a table |
| or a function, register it the same way you would register a CSV file. No |
| extra setup: |
| |
| ```python |
| from datafusion import SessionContext |
| import my_tables |
| |
| ctx = SessionContext() |
| ctx.register_table("events", my_tables.TableProvider("s3://bucket/events")) |
| ctx.sql("SELECT count(*) FROM events").show() |
| ``` |
| |
| **Libraries that change how queries run need to be installed on the session.** |
| A distributed engine, or anything that rewrites your query plan, has to be |
| attached to the session before it can do its work. That is what |
| {py:meth}`~datafusion.SessionContext.with_extensions` is for. The library |
| documents an object — often called `Extension` — that you pass to it: |
| |
| ```python |
| from datafusion import SessionContext |
| import my_engine |
| |
| ctx = SessionContext().with_extensions(my_engine.Extension("scheduler:50050")) |
| ctx.register_table("events", my_engine.TableProvider("s3://bucket/events")) |
| ctx.sql("SELECT count(*) FROM events").show() |
| ``` |
| |
| `with_extensions` returns a context; use the returned one. It shares |
| everything else with the context you called it on, so tables you registered |
| before the call are still there. |
| |
| ## Using more than one library |
| |
| Pass them all to a single call: |
| |
| ```python |
| ctx = SessionContext().with_extensions( |
| my_tables.Extension(), |
| my_engine.Extension("scheduler:50050"), |
| ) |
| ``` |
| |
| One call rather than several is worth preferring: it lets the libraries see |
| each other, which they cannot do if you install them one at a time. Order |
| rarely matters. When a library needs a particular position — usually "list me |
| last" for something that wraps the others — it says so in its own |
| documentation. |
| |
| ## Two things that will bite you |
| |
| **Keep your context alive.** A `DataFrame` or a plan does not keep its session |
| alive on its own. If a context is garbage-collected while something built from |
| it is still in use, the next query fails with: |
| |
| ```text |
| TaskContextProvider went out of scope over FFI boundary |
| ``` |
| |
| Almost always this is a helper that built a context locally and returned a |
| DataFrame: |
| |
| ```python |
| # Wrong — ctx is collected when the function returns. |
| def load(): |
| ctx = SessionContext().with_extensions(my_engine.Extension()) |
| return ctx.sql("SELECT * FROM events") |
| |
| # Right — hand back the context too, or keep it on an object that lives |
| # as long as the frames derived from it. |
| def load(): |
| ctx = SessionContext().with_extensions(my_engine.Extension()) |
| return ctx, ctx.sql("SELECT * FROM events") |
| ``` |
| |
| **Versions have to match.** An extension library is compiled against one |
| DataFusion version. A mismatch raises an `ImportError` naming the version it |
| found and the version expected, at the moment you register or install the |
| library — not silently at query time. If you see one, upgrade or downgrade the |
| extension library so its DataFusion version matches this package's. See |
| {ref}`extension_version_mismatch`. |
| |
| ## Checking what a session knows about |
| |
| {py:meth}`~datafusion.SessionContext.logical_extension_codec_ids` and |
| {py:meth}`~datafusion.SessionContext.physical_extension_codec_ids` list which |
| libraries a session has been taught about. Useful when a query fails and you |
| want to confirm the library actually got installed: |
| |
| ```python |
| ctx = SessionContext().with_extensions(my_engine.Extension()) |
| ctx.logical_extension_codec_ids() |
| # ['my_engine.LogicalCodec'] |
| ``` |
| |
| An empty list means nothing extra is installed. |
| |
| ## Next steps |
| |
| - {ref}`distributed_query_engines` — running your queries across several |
| machines with an engine library. |
| - {ref}`distributed_expressions` — the other road to parallelism, where you |
| decide the partitioning and ship expressions to a worker pool. |
| - {ref}`ffi` — writing an extension library of your own. |