In-memory InfluxDB client for fast, isolated testing.
Stores data in ETS tables, enabling safe async: true tests with full
isolation between test instances. Each call to start/1 creates an
independent ETS table.
Parses real line protocol on write, stores points as maps, and responds
with realistic InfluxDB response formats on query. Parsing is split out:
InfluxElixir.Client.Local.LineProtocolParser handles writes and
InfluxElixir.Client.Local.SQLParser handles the SQL subset;
InfluxElixir.Client.Local.Store owns the ETS table; this module
owns profiles, the write rules and the InfluxQL and Flux paths; SQL execution is
InfluxElixir.Client.Local.SQLExecutor.
Profiles
LocalClient enforces an InfluxDB version profile that determines which operations are available. This prevents tests from accidentally using operations that the real InfluxDB backend doesn't support.
| Profile | Write | SQL | InfluxQL | Flux | DB CRUD | Bucket CRUD | Tokens |
|---|---|---|---|---|---|---|---|
:v3_core | yes | yes | yes | no | yes | no | no |
:v3_enterprise | yes | yes | yes | no | yes | no | yes |
:v2 | yes | no | no | yes | no | yes | no |
Operations outside the configured profile return
{:error, :unsupported_operation}.
Usage
# Match your production InfluxDB version
setup do
{:ok, conn} = InfluxElixir.Client.Local.start(
databases: ["test_db"],
profile: :v3_core
)
on_exit(fn -> InfluxElixir.Client.Local.stop(conn) end)
{:ok, conn: conn}
endChecking Profile Support
Use supports?/2 to check if an operation is available:
if Local.supports?(conn, :query_sql) do
Local.query_sql(conn, "SELECT * FROM cpu", database: "test_db")
endStorage
Each instance is one ETS table owned by InfluxElixir.Client.Local.Store,
which alone knows the key layout. Every mutation is a single insert or
delete of its own key, so concurrent writers — async: true tests sharing
one database, BatchWriter flushes racing direct writes — never
read-modify-write a shared value and no write is ever lost.
Write Rules
What a write accepts is what InfluxDB 3 accepts, verified against the engine:
- A payload is applied line by line. A line with a syntax error, or a
column whose kind conflicts with the measurement's schema, is dropped
and reported; the other lines are stored. The result is then
{:error, %{status: 400, body: json}}with the engine's body —"partial write of line protocol occurred"and onedataentry per bad line (error_message,line_number,original_line). - A column's kind is fixed by the first write that names it, per
database and measurement: a tag stays a tag, an integer field stays
an integer (
v=1ithenv=2.0is "invalid column type for column 'v', expected iox::column_type::field::integer, got iox::column_type::field::float"). Deleting the database drops the schema with the data. timeis a reserved column ("'time' is a reserved column" on a new table; on an existing one the engine words it as a column-type conflict withiox::column_type::timestamp); a key cannot be both a tag and a field on one line; an integer must fit in 64 bits (7uis unsigned); a newline inside a quoted string value is part of the value; a measurement, tag key, tag value or field key ending in a backslash is refused ("... may not end with a backslash"); an empty payload is "incoming write was empty".- Points with the same measurement, tag set and timestamp are one point,
on both versions (verified): their fields merge and the later write
wins per field —
v=1i,w=1ithenv=2iat the same instant reads back asv=2, w=1, and the last of two such lines in one payload wins. A different tag value is a different point.DELETEremoves the merged point. accept_partial: falsemakes a write all-or-nothing: the first bad line in line order — a parse error or a schema conflict, also against an earlier line of the same payload — rejects it, nothing is stored (not even schema), and the body is{"error": "line protocol parsing error", "data": {...}}with that one line.no_sync:is accepted and changes nothing (the double has no write-ahead log; on the engine ano_syncwrite may not be visible to a query right away). Either one that is not a boolean is the engine's 400.- A schema error's
original_lineis the line as the engine renders it, not as it was sent: single spaces, floats shortest and without an exponent (2.0→2), strings unquoted. Runs of spaces between a line's sections are one separator. - Under the
:v2profile the rules are InfluxDB 2's, verified against 2.7: a field type conflict is HTTP 422 ({"code":"unprocessable entity","message":"... field type conflict: input field "v" on measurement "m" is type float, already exists as type integer dropped=N"}) with the other lines stored; a line that fails to parse rejects the whole payload with HTTP 400 ({"code":"invalid","message": "unable to parse '<line>': ..."}) and nothing is stored;timeas a field is dropped silently and as a tag is a 400; a tag and a field may share a name; an empty payload is accepted.
SQL Query Support
query_sql/3 understands a subset of SQL.
Identifiers follow DataFusion: an unquoted name is folded to lower case
(SELECT Host FROM Cpu reads column host of table cpu, and v AS V
answers "v"), a double-quoted one is exact ("Host"), and "..." is
never a string — WHERE k = "a" compares with column a. See
InfluxElixir.Client.Local.SQLIdentifiers. (InfluxQL identifiers are
case-sensitive and are not folded.) The subset:
SELECT * FROM measurementSELECT col1, col2 [, ...] FROM measurementwith optionalAS alias(projects fields and tags;timeis selectable).timeandDATE_BINbuckets areDateTimevalues with microsecond precision, the same as the HTTP and Flight transports return; compare them withDateTime.compare/2or a six-digit sigil (~U[... .000000Z]). A projected column may be an arithmetic expression with an alias ((bid + ask) / 2 AS mid;+ - * / %and unary minus,%taking the dividend's sign); a null operand makes the column null (omitted).ORDER BYmay name a projected alias.WITH name AS (<select>)[, name AS (<select>)] <select>— non-recursive CTEs. Each body is a query in this subset, run in order over the store or an earlier CTE; the finalSELECTmay read from any of them (FROM w). A CTE's output columns are its fields (timestaystime).FROM a CROSS JOIN b— every row ofapaired with every row ofb(the usual use is broadcasting a one-row CTE such as a median across the rows it screens). A column present on both sides is refused as ambiguous, because qualifiers are dropped and the two could not be told apart; the engine refuses the unqualified reference too. Other joins, set operations,HAVINGand window functions are rejected by name rather than silently ignored.- Table qualifiers and aliases:
FROM q AS w/FROM q w, andw.time,q.bidin any clause — one table per query, so the prefix is dropped. WHEREwith=,!=/<>,<,<=,>,>=, combined withAND,OR,NOTand parentheses (ANDbinds tighter thanOR, as in SQL). A quoted literal is always a string, exactly as in InfluxDB v3:'08338636'keeps its leading zero and matches a string tag, and comparing it against a numeric field compares the field's text rendering (soamount >= '1000.00'is a lexical comparison — DataFusion casts the numeric side to Utf8). The other way round, a string column against a bare number compares the number's text rendering, also lexically (rack = 2matches the tag"2";rack > 3does not match"10"). Bare literals (42,1.5,true) are typed and compare numerically against numeric fields. Either side may be an arithmetic expression over columns (price <= med * 3,2 * price > volume); a bare word is a column reference, as in SQL. A column that no row has — named anywhere:SELECT, an aggregate,WHERE,GROUP BY,ORDER BY,DISTINCT— is the engine's schema error ("No field named prod", HTTP 500), which is what a typo or a forgotten pair of quotes produces in production. With no rows the schema is unknown and nothing is checked.col = NULL(anilparam) is never true. Logic is SQL's three-valued logic: a comparison with a null operand is unknown,NOTkeeps it unknown, and only a true predicate keeps the row, soNOT (rack = '1')does not return rows without arack. A boolean column is itself a predicate (WHERE b,NOT b); any other bare column is the engine's planning error ("Cannot create filter with non-boolean predicate 't.n' returning Int64").WHERE col IN (v1, v2, ...)andWHERE col NOT IN (v1, v2, ...)— each item a literal, a column or an expression, as in SQL (a bare word is a column reference, never a string)- A constant with an alias in any select list (
0.0 AS volume,'x' AS label); an unaliased constant is refused because DataFusion names it after its own rendering WHERE col IS NULLandWHERE col IS NOT NULLWHERE col [NOT] BETWEEN low AND high(inclusive;timetoo)WHERE col [NOT] LIKE 'pattern'andILIKE(%any run,_one character, a backslash makes the next character literal —'al\%%';LIKEis case-sensitive,ILIKEis not).LIKEover a numeric column is the engine's planning error, reproduced.WHERE time <op> <comparand>— exactly what InfluxDB 3 accepts against a Timestamp: a quoted ISO-8601 datetime ('2026-03-31T12:00:00Z', zone-less or fractional forms too), a quoted date ('2026-03-31', midnight UTC), ornow()offset by+/-INTERVAL 'N unit'terms (now() - INTERVAL '5 minutes'). A bare integer (time > 1700000000) and an integer-as-string are rejected, as DataFusion rejects them ("Cannot infer common argument type for comparison operation Timestamp(ns) > Int64"), rather than silently matching nothing.SELECT DISTINCT col[, col ...] FROM measurement(sorted combinations;ORDER BYmust name a selected column, as in DataFusion). An all-null combination is a row too (%{}).SELECT DISTINCT ON (col[, col ...]) ...over plain or projected columns (or*): the first row per distinct key afterORDER BY, thenLIMIT/OFFSET—ORDER BY k, time DESCis the latest row perk. As on the engine, anORDER BYmust start with theONcolumns (400), resolves against the table rather than select aliases (500), and aggregates orGROUP BYare refused (405). WithoutORDER BYthe engine's choice and order are unspecified. An expression inON(DATE_BIN(...)) is refused by name.ORDER BY a [ASC|DESC][, b [ASC|DESC] ...]— each termtime, a column, an output alias, or (on raw and projected rows) an expression such asCAST(level AS INTEGER) DESC; every term applies, each with its own direction. Nulls sort last ascending and first descending, unless a term saysNULLS FIRST/NULLS LAST.CAST(expr AS INTEGER | INT | BIGINT | DOUBLE | FLOAT | VARCHAR | STRING)and DataFusion'scol::TYPEshorthand, wherever an expression is allowed:WHERE(CAST(level AS INTEGER) <= 20compares a numeric tag numerically),BETWEEN,LIKE, projections, aggregates, arithmetic andORDER BY. Text converts only when the whole string is a number, a float truncates to an integer, a number renders to text, null stays null. A cast that cannot be performed ('abc'toINTEGER,timetoINTEGER) makes InfluxDB 3 Core drop the connection mid-response, whichClient.HTTPreports as{:error, {:connection_error, %Mint.TransportError{reason: :closed}}}; the double reports{:error, {:connection_error, :closed}}.BOOLEANandTIMESTAMPtargets are outside the subset.LIMIT nandOFFSET m, in either order —OFFSETskips rows beforeLIMITtakes them, on plain, projected, grouped andDISTINCTrows alike;LIMIT 0returns no rows; a negative or non-numeric limit is rejected, as the engine rejects it$paramplaceholders viaparams: %{"$name" => value}in opts. ADateTime,NaiveDateTimeorDateparam renders as the ISO-8601 string Jason sends over HTTP, sotime >= $startworks the same on both clients; an integer param againsttimeis rejected on both.DATE_BIN(INTERVAL 'N unit', time)time bucketing- Aggregate functions:
AVG,SUM,COUNT,MIN,MAX,MEDIAN(the middle value; for an even count the mean of the two middle values in the column's type, so two integers average with integer division),STDDEV/STDDEV_SAMP(sample),STDDEV_POP,VAR/VAR_SAMP(sample),VAR_POP. The argument may be an arithmetic expression over fields and numeric literals (SUM(value * value),AVG(bid + ask)); two integer operands divide as integers (3 / 2 = 1), as in DataFusion. Division by zero is null in the double, where InfluxDB returns IEEE infinity for floats (serialised as JSONnullbut counted byCOUNT) and fails the query for integers. A sample statistic over one value is null.COUNT(DISTINCT col)counts distinct non-null values.MIN(time),MAX(time)andCOUNT(time)work (aDateTimeresult); every other aggregate overtime, and any arithmetic on it, is rejected as DataFusion rejects it. - Selector functions:
selector_first|last|min|max(field, time)['value']and['time']; without a subscript, the engine's struct%{"time" => %DateTime{}, "value" => v} - Ordered aggregates:
first_value(field ORDER BY col [ASC|DESC])andlast_value(field ORDER BY col [ASC|DESC])— the InfluxDB v3 SQL (DataFusion) spelling. TheORDER BYis required: without it the real engine returns an arbitrary row from the group, which the double cannot reproduce, so it rejects the query rather than certify a non-deterministic result. InfluxQL-styleFIRST(f, t)/LAST(f, t)are rejected because InfluxDB v3 SQL has no such functions. GROUP BY DATE_BIN(INTERVAL 'N unit', time)— optional. When omitted, aggregate queries return a single scalar row (COUNTover an empty result set is0; other aggregates returnnil).GROUP BY <col>[, <col>...]— bucket points by tag/field values, with or without an aggregate (SELECT host FROM m GROUP BY hostis one row per host). Bare column names (with optionalAS alias) are valid in theSELECTlist only when grouped; a projected column that is neither grouped nor aggregated is the engine's planning error ("must appear in the GROUP BY clause or must be part of an aggregate function").ORDER BYapplies to grouped rows too. Grouping columns combine withDATE_BIN(a row per bucket per value).- A
GROUP BYitem may be a select alias (GROUP BY bucket) or a 1-based position (GROUP BY 1, 2), and anORDER BYitem a position (ORDER BY 2 DESC), as DataFusion resolves them; a position outside the select list is the engine's planning error. - Interval units:
seconds,minutes,hours,days
A null column is omitted from the row rather than present as nil,
exactly as InfluxDB 3's JSON and JSONL responses do (COUNT is 0, never
null).
format: is answered as Client.HTTP answers it — :csv rows carry
the engine's CSV strings, :parquet is refused by name, an unknown
format is the engine's 400: see InfluxElixir.Client.Local.Format.
Anything outside this subset is rejected with
{:error, %{status: 400, body: "Client.Local: ..."}}. The Client.Local:
prefix marks the rejection as a limitation of the test double rather than
of InfluxDB — the real engine may well accept the query. check_sql/1
answers the same question without executing, so a test can skip with a
reason and the query can be covered in the integration tier instead.
SQL Param Types
params: values are serialised to SQL literals before query execution.
Supported types: binary, integer, float, boolean, and Decimal
(when the optional :decimal dependency is loaded — Decimal values
are emitted as bare numeric literals via Decimal.to_string(:normal)).
Gzip Decompression
If a write payload begins with gzip magic bytes (0x1F 0x8B) it is automatically decompressed before line protocol parsing.
Timestamp Precision
Pass precision: in opts to say what unit numeric timestamps are in.
The spellings are the engine's, verified: InfluxDB 3 (:v3_core,
:v3_enterprise) takes ns | n | nanosecond | us | u | microsecond | ms | millisecond | s | second | auto as an atom or a string, where
auto guesses the unit from the magnitude (below 5e9 seconds, 5e12
milliseconds, 5e15 microseconds, else nanoseconds); anything else is the
engine's 400 serde error: unknown variant. InfluxDB 2 (:v2) takes
ns | us | ms | s and the long names HTTP.write/3 maps onto them, and
answers 400 invalid precision to the rest. Default: nanoseconds.
Summary
Functions
Reports whether the SQL subset can express sql, without executing it.
Creates a named bucket in this local instance.
Creates a named database in this local instance.
Creates a synthetic API token and stores it in ETS.
Deletes a bucket from this local instance.
Deletes a database from this local instance.
Deletes a token by its id field. Returns :ok even if the token was
not found, matching real InfluxDB delete semantics.
@doc """ Executes a SQL statement as InfluxDB 3 does (verified against Core).
Returns a passing health status in the shape Client.HTTP returns for
the profile's server (verified): InfluxDB 3's /health answers a plain
OK, which the HTTP client reports as %{"status" => "pass"}; InfluxDB
2 answers JSON with name, message, status, checks, version and
commit (here "local").
Returns all buckets in this local instance as maps with "id", "name"
and "retentionRules" ([%{"type" => "expire", "everySeconds" => n}],
the shape InfluxDB 2 lists; verified).
Returns the databases as maps with a single "name" key, sorted, with
the engine's own _internal among them as InfluxDB 3 lists it.
Executes a Flux query as InfluxDB 2 does; see InfluxElixir.Client.Local.Flux
for the stages supported. Every stage is applied or the query is refused
({:error, %{status: 400, body: json}} naming it) — a stage is never
skipped. Rows use the engine's long shape, one per field value
Executes an InfluxQL query.
Executes a SQL query against the stored points and returns rows shaped as InfluxDB 3 returns them.
Executes a SQL query and returns results as a lazy Stream.
Starts a new LocalClient instance with isolated ETS storage.
Stops a LocalClient instance and cleans up its ETS table.
Returns true if the given operation is supported by the connection's profile.
Parses line protocol binary and stores the resulting points in ETS.
Types
@type conn() :: %{ table: InfluxElixir.Client.Local.Store.t(), database: binary() | nil, profile: profile() }
@type point_map() :: InfluxElixir.Client.Local.LineProtocolParser.point()
@type profile() :: :v3_core | :v3_enterprise | :v2
Functions
Reports whether the SQL subset can express sql, without executing it.
Returns :ok or the same {:error, %{status: 400, body: "Client.Local: ..."}}
that query_sql/3 would return. Use it to skip a test with a reason
instead of tagging it excluded:
case InfluxElixir.Client.Local.check_sql(sql) do
:ok -> run_against_local(sql)
{:error, %{body: why}} -> ExUnit.Callbacks.on_exit(fn -> :ok end); flunk(why)
endQueries outside the subset (CTEs, joins, window functions, median, ...)
belong in an integration test against a real InfluxDB; see the testing
guide.
@spec create_bucket( InfluxElixir.Client.connection(), binary(), keyword() ) :: :ok | {:error, term()}
Creates a named bucket in this local instance.
retention: is the expiry in seconds (default 0, none). InfluxDB 2
refuses a period between 1 and 3599 seconds with a 500 retention policy duration must be at least 1h0m0s (verified), and so does this.
Creating an already-existing bucket is idempotent.
@spec create_database( InfluxElixir.Client.connection(), binary(), keyword() ) :: :ok | {:error, term()}
Creates a named database in this local instance.
Creating an existing database is :ok (the engine's 409, which
Client.HTTP treats as success). A name the engine refuses is its 400,
and a sixth database on the :v3_core profile its 422 — see
InfluxElixir.Client.Local.DatabaseRules. retention: must be a
duration the engine reads ("30d", "1h 30m", "1.5h", "0"), or it
is the engine's 400; the double keeps no retention, so nothing expires.
@spec create_token( InfluxElixir.Client.connection(), binary(), keyword() ) :: {:ok, map()} | {:error, term()}
Creates a synthetic API token and stores it in ETS.
Returns {:ok, %{id: id, token: token_string, description: desc}}.
@spec delete_bucket(InfluxElixir.Client.connection(), binary()) :: :ok | {:error, term()}
Deletes a bucket from this local instance.
Returns {:error, %{status: 404, body: "bucket not found: name"}} for a
bucket that does not exist — InfluxDB 2 answers 404 (verified), which
InfluxElixir.Client.HTTP reports with this body.
@spec delete_database(InfluxElixir.Client.connection(), binary()) :: :ok | {:error, term()}
Deletes a database from this local instance.
Returns {:error, %{status: 404, body: "the requested resource was not found: name"}} — the engine's answer (verified) — if the database does
not exist.
@spec delete_token(InfluxElixir.Client.connection(), binary()) :: :ok | {:error, term()}
Deletes a token by its id field. Returns :ok even if the token was
not found, matching real InfluxDB delete semantics.
@spec execute_sql(InfluxElixir.Client.connection(), binary(), keyword()) :: {:ok, map() | [map()]} | {:error, term()}
@doc """ Executes a SQL statement as InfluxDB 3 does (verified against Core).
SELECT/WITH ... SELECTrun asquery_sql/3and return{:ok, rows}.DELETE FROM m [WHERE ...]on:v3_enterpriseremoves the matching points and returns{:ok, %{"rows_affected" => n}}. Identifiers follow SQL's rules, as in aSELECT(DELETE FROM "Cpu"), and a table whose every point was deleted stays, answering no rows. (Not verified: no Enterprise server was available.)- Everything else is refused with the engine's answer:
DELETE,INSERTandUPDATEare 400Error during planning: DML not supported: Delete | Insert Into | Update;CREATE TABLE | VIEW | DATABASEandDROP TABLE | VIEWare 400Error during planning: DDL not supported: CreateMemoryTable | CreateView | CreateCatalog | DropTable | DropView; any other statement (ALTER,TRUNCATE, ...) is 405This feature is not implemented: Unsupported SQL statement: <sql>.
@spec health(InfluxElixir.Client.connection()) :: {:ok, map()} | {:error, term()}
Returns a passing health status in the shape Client.HTTP returns for
the profile's server (verified): InfluxDB 3's /health answers a plain
OK, which the HTTP client reports as %{"status" => "pass"}; InfluxDB
2 answers JSON with name, message, status, checks, version and
commit (here "local").
@spec list_buckets(InfluxElixir.Client.connection()) :: {:ok, [map()]} | {:error, term()}
Returns all buckets in this local instance as maps with "id", "name"
and "retentionRules" ([%{"type" => "expire", "everySeconds" => n}],
the shape InfluxDB 2 lists; verified).
@spec list_databases(InfluxElixir.Client.connection()) :: {:ok, [map()]} | {:error, term()}
Returns the databases as maps with a single "name" key, sorted, with
the engine's own _internal among them as InfluxDB 3 lists it.
@spec query_flux(InfluxElixir.Client.connection(), binary(), keyword()) :: InfluxElixir.Client.query_result()
Executes a Flux query as InfluxDB 2 does; see InfluxElixir.Client.Local.Flux
for the stages supported. Every stage is applied or the query is refused
({:error, %{status: 400, body: json}} naming it) — a stage is never
skipped. Rows use the engine's long shape, one per field value:
%{"result" => "_result", "table" => 0, "_start" => %DateTime{},
"_stop" => %DateTime{}, "_time" => %DateTime{}, "_measurement" => "cpu",
"_field" => "value", "_value" => 1.0, "host" => "web01"}A bucket that does not exist is the engine's 404.
@spec query_influxql( InfluxElixir.Client.connection(), binary(), keyword() ) :: InfluxElixir.Client.query_result()
Executes an InfluxQL query.
Answers what InfluxDB 3 answers, verified against the engine:
SHOW DATABASES—%{"iox::database" => name, "deleted" => false}SHOW MEASUREMENTS—%{"iox::measurement" => "measurements", "name" => m}SHOW TAG KEYS [FROM m]—%{"iox::measurement" => m, "tagKey" => k}SHOW FIELD KEYS [FROM m]—%{"iox::measurement" => m, "fieldKey" => k, "fieldType" => "integer" | "unsigned" | "float" | "string" | "boolean"}SELECT ...— InfluxQL, not SQL: seeInfluxElixir.Client.Local.InfluxQLfor the row shape (iox::measurementandtimeon every row, time order,mean/count/... aggregates, an unknown column or measurement is{:ok, []}) and for what is refused by name
@spec query_sql(InfluxElixir.Client.connection(), binary(), keyword()) :: InfluxElixir.Client.query_result()
Executes a SQL query against the stored points and returns rows shaped as InfluxDB 3 returns them.
The SQL subset, and what the double refuses by name, is described under
"SQL" in the moduledoc; check_sql/1 answers without running. $name
placeholders take params: %{"name" => value} (or a keyword list).
@spec query_sql_stream( InfluxElixir.Client.connection(), binary(), keyword() ) :: Enumerable.t()
Executes a SQL query and returns results as a lazy Stream.
Delegates to query_sql/3 then wraps the list in a stream.
Mirrors the error semantics of the HTTP client's streaming query:
because the return type is an Enumerable.t(), errors cannot be returned as a
tuple. Instead a failure — an underlying query error or an operation the
connection's profile does not support — is raised as an
InfluxElixir.StreamError when the stream is enumerated, never swallowed as an
empty result. This keeps Client.Local a faithful drop-in test double for
Client.HTTP, so consumer code that rescues InfluxElixir.StreamError can be
exercised against it.
Starts a new LocalClient instance with isolated ETS storage.
Options
:database- connection-level default database name. Used when the caller does not passdatabase:in opts. Pre-created automatically.:databases- list of database names to pre-create (default:[]). Without:database, the first is the default, as inClient.HTTP. With neither there is no default: an operation that needs a database is{:error, :no_database_specified}, as over HTTP.
On the v3 profiles each name must be one InfluxDB 3 accepts, and
:v3_core holds at most 5; start/1 raises ArgumentError with the
engine's message otherwise (see InfluxElixir.Client.Local.DatabaseRules).
:profile- InfluxDB version profile to emulate. Determines which operations are available. Operations outside the profile return{:error, :unsupported_operation}. Valid values::v3_core(default) — write, SQL, InfluxQL, database CRUD:v3_enterprise— everything in v3_core plus token management:v2— write, Flux, bucket CRUD
Examples
iex> {:ok, conn} = InfluxElixir.Client.Local.start(databases: ["mydb"])
iex> conn.profile
:v3_core
iex> {:ok, conn} = InfluxElixir.Client.Local.start(profile: :v2)
iex> conn.profile
:v2
iex> {:ok, conn} = InfluxElixir.Client.Local.start(database: "metrics")
iex> conn.database
"metrics"
@spec stop(conn()) :: :ok
Stops a LocalClient instance and cleans up its ETS table.
Safe to call multiple times; a no-op if the table is already deleted.
Returns true if the given operation is supported by the connection's profile.
@spec write(InfluxElixir.Client.connection(), binary(), keyword()) :: InfluxElixir.Client.write_result()
Parses line protocol binary and stores the resulting points in ETS.
The database is read from opts[:database]. If the database does not
exist an {:error, %{status: 404, body: ...}} is returned. If line protocol
cannot be parsed an {:error, %{status: 400, body: ...}} is returned.
Payloads beginning with gzip magic bytes are automatically decompressed.
Pass precision: to say what unit numeric timestamps are in (default
nanoseconds); see "Timestamp Precision" in the moduledoc for the
spellings each profile accepts and auto.