TuringDB home pagelight logodark logo
  • Discord
  • GitHub
  • Website
TuringDB

Get Started

  • Introduction
  • Get Started
  • Claude Code Skill
  • Commands

Concepts

  • Overview
  • A Columnar Graph Database
  • Versioning System
  • The DataPart System
  • Zero-Locking Architecture
  • Snapshots Isolation

Benchmarks

  • Results Summary
  • Technical Report

Query Language

  • Overview
  • Matching
  • Paths
  • Results and Aggregation
  • Subqueries
  • Functions and Types
  • Writing Data
  • Procedures and Commands
  • CheatSheet

Importing data

  • JSONL
  • CSV
  • Neo4j
  • GML
  • Parquet

Graph Development

  • Create Graph
  • Load Graph
  • Load External data
  • Create a Change
  • List Changes
  • Create Nodes and Edges
  • Update Properties
  • Submit a Change
  • Time Travel
  • Graph Examples

Vector Search

  • Vector Search

Graph Algorithms

  • Shortest Path

Tutorials

  • Example Notebooks

Python SDK

  • Get Started
  • Reference

Troubleshooting

  • Troubleshooting

  • LOAD PARQUET
  • Required columns
  • Property columns
  • From pandas
  • turing-parquet
  • When to use it
  • Quick usage
  • Expected file format
  • Import steps
  • Sub-record expansion
  • Mapping rules
  • Example
  • Deduplication in action
  • Verify the import
Importing data

Import Parquet files

Build a TuringDB graph from node and edge Parquet files with LOAD PARQUET or the turing-parquet CLI

To load Parquet files containing columns for node IDs, node properties, edge source and target IDs, and edge properties, see LOAD PARQUET.

To load Parquet files containing a JSON string column, which is then converted into a graph structure, see turing-parquet.

​
LOAD PARQUET

LOAD PARQUET is the fastest way to get data into TuringDB, and the recommended path for anything beyond a few thousand entities.

LOAD PARQUET '<directory>' [AS <graph name>] [WITH DURATIONS [<column>, ...]]

The graph name defaults to the name of the directory. The directory is resolved inside the data subdirectory of the TuringDB working directory (-turing-dir) and must contain nodes.parquet and edges.parquet.

LOAD PARQUET creates a new graph, so choose a graph name that does not exist yet. To add a large volume of data to an existing graph, write the existing data plus the new data to Parquet and load it under a new graph name. Use Cypher writes only for small, incremental changes.

​
Required columns

nodes.parquet has one row per node:

  • __id: INT64, unique. Your own node key. Edges refer to it.
  • __labels: a list of strings (list<string>), non-empty. Each element is a label of the node. The path of each element must be __labels.list.element.

edges.parquet has one row per edge:

  • __source: INT64, the __id of the source node.
  • __target: INT64, the __id of the target node.
  • __type: a string, the edge type. Every edge has exactly one.

​
Property columns

Every other column becomes a property of the node or edge on that row. The column name is the property name. Null values are allowed.

Parquet typeTuringDB property type
INT64Integer
DOUBLEDouble
BOOLEANBoolean
BYTE_ARRAY (string)String
timestampDateTime
list of the types aboveList

Use 64-bit columns: cast INT32 and FLOAT columns to int64 / float64 first. To load an INT64 column of microseconds as a Duration, name it in WITH DURATIONS [<column>].

​
From pandas

To go from CSV, JSON, SQL extracts or API data, load it into pandas, map your natural keys to INT64 ids, and write the two files:

import pandas as pd, pyarrow as pa, pyarrow.parquet as pq
from pathlib import Path

people = pd.read_csv("people.csv")   # columns: email, name, age
knows = pd.read_csv("knows.csv")     # columns: src, dst, since  (src and dst are emails)

ids = {key: i for i, key in enumerate(people["email"])}            # natural key -> int64 __id
nodes = pd.DataFrame({
    "__id": pd.Series(range(len(people)), dtype="int64"),
    "__labels": [["Person"]] * len(people),
    "email": people["email"],
    "name": people["name"],
    "age": people["age"].astype("Int64"),                          # int64
})
edges = pd.DataFrame({
    "__source": knows["src"].map(ids).astype("int64"),
    "__target": knows["dst"].map(ids).astype("int64"),
    "__type": "KNOWS",
    "since": knows["since"].astype("int64"),
})

out = Path.home() / ".turing" / "data" / "people_graph"            # <turing-dir>/data/<directory>
out.mkdir(parents=True, exist_ok=True)
pq.write_table(pa.Table.from_pandas(nodes, preserve_index=False), out / "nodes.parquet")
pq.write_table(pa.Table.from_pandas(edges, preserve_index=False), out / "edges.parquet")

client.query("LOAD PARQUET 'people_graph' AS people")
client.set_graph("people")

For several node types, concatenate them into a single nodes.parquet with non-overlapping __id ranges, each row carrying its own __labels. Keep the natural key (email above) as a property so you can match on it in later queries.

​
turing-parquet

turing-parquet is a CLI tool that reads node and edge Parquet files and writes a TuringDB graph to disk. Copy the resulting graph directory into your TuringDB working directory and open it with turingdb.

​
When to use it

  • You have one or more Parquet tables of nodes and edges (a common shape for biomedical, financial, or web-scale knowledge graphs).
  • Per-row properties are encoded as a JSON string column (typically named properties), possibly containing nested objects and arrays.
  • You want nested JSON sub-records to be expanded into their own nodes with HAS_* edges, with automatic deduplication of repeated values.

​
Quick usage

turing-parquet \
    -nodes data/nodes.parquet \
    -edges data/edges.parquet \
    -out  ./turingdb.out \
    -graph mygraph

This writes the graph mygraph under ./turingdb.out/graphs/. Move that subdirectory into your TuringDB working directory (default $HOME/.turing/graphs/) and load it with load graph mygraph.

​
Expected file format

turing-parquet expects a fixed set of top-level columns on each file. Other columns are ignored.

Node files (-nodes):

ColumnRequirement
idRequired. String. Unique node identifier referenced by edges.
labelRequired. String. Becomes the node label in TuringDB (e.g. GEN, DIS, DRG).
properties col.Required. JSON string. Default column name is properties; override with -props COLUMN.

Edge files (-edges):

ColumnRequirement
fromRequired. String. Source node id.
toRequired. String. Target node id.
edge-type col.Required. String. Becomes the edge type in TuringDB. Default column name is relation; override with -edgetype COLUMN.
properties col.Required. JSON string. Same column as nodes (default properties).

The properties column is parsed as JSON. Scalar fields become node/edge properties; nested objects become sub-record nodes linked by HAS_<FIELD> edges; arrays of objects become multiple sub-record nodes. Identical sub-records (same inferred label + id/value) are deduplicated into a single shared node.

​
Import steps

The example below uses the OptimusKG biomedical knowledge graph (190,531 nodes across 10 entity types and ~21.8M edges across 26 relation types), shipped as nodes.parquet and edges.parquet.

1

Run turing-parquet against your files

turing-parquet \
    -nodes data/nodes.parquet \
    -edges data/edges.parquet \
    -out  ./turingdb.out \
    -graph optimuskg

What each flag does:

FlagPurpose
-nodesPath to a node Parquet file. Repeatable. Pass -nodes multiple times for sharded inputs.
-edgesPath to an edge Parquet file. Repeatable.
-propsColumn name to merge JSON property analysis on. Defaults to properties when present in every input; otherwise the tool prompts.
-edgetypeColumn name to derive edge types from. Defaults to relation when present in any edge file; otherwise the tool prompts.
-outTuringDB root directory (default ./turingdb.out). Created if absent; existing graphs in the same directory are preserved.
-graphGraph name to write inside -out (default imported). If a graph with this name already exists in the directory, its subdirectory is wiped before writing.

The tool prints the inferred schema, a JSON property-type breakdown, an edge-type histogram, and the final node/edge/sub-record counts.

2

Copy the graph into your TuringDB working directory

cp -r ./turingdb.out/graphs/optimuskg ~/.turing/graphs/

(Or point turingdb start at the import directory directly with -turing-dir ./turingdb.out.)

3

Load the graph and query it

  • Cypher
  • Python SDK
load graph optimuskg

cd optimuskg

MATCH (n:GEN) WHERE n.symbol = 'TSPAN6' RETURN n.id, n.symbol, n.biotype
from turingdb import TuringDB

client = TuringDB(host="http://localhost:6666")
client.load_graph("optimuskg")
client.set_graph("optimuskg")

df = client.query(
    "MATCH (n:GEN) WHERE n.symbol = 'TSPAN6' "
    "RETURN n.id, n.symbol, n.biotype"
)
print(df)

Your Parquet files are now a queryable TuringDB graph.

​
Sub-record expansion

Because the properties column is JSON, nested structure in the source data is preserved as part of the graph. For OptimusKG, this turns 190,531 source rows into about 5M nodes.

​
Mapping rules

  • Each scalar JSON field on a node becomes a property on that node.
  • Each nested object becomes a separate node linked by a HAS_<FIELD> edge.
  • Each array of objects becomes multiple sub-record nodes via repeated HAS_<FIELD> edges.
  • Sub-records with the same inferred label and id/value are deduplicated into a single shared node. A handful of distinct :Tractability values can be referenced by hundreds of thousands of genes.

The mapping is printed during import so you can see the inferred sub-record labels and which properties land where.

​
Example

Take a single row in nodes.parquet for the gene TSPAN6:

idlabelproperties
ENSG00000000003GEN{"symbol":"TSPAN6","biotype":"protein_coding","tractability":{"id":"T1","score":"high","modality":"small molecule"},"synonyms":[{"value":"T245"},{"value":"TM4SF6"}]}

The properties JSON, pretty-printed:

{
  "symbol": "TSPAN6",
  "biotype": "protein_coding",
  "tractability": {
    "id": "T1",
    "score": "high",
    "modality": "small molecule"
  },
  "synonyms": [
    {"value": "T245"},
    {"value": "TM4SF6"}
  ]
}

After import, this single row becomes four nodes and three edges:

How each piece of the source row maps:

SourceBecomes
id, labelThe :GEN node’s identity and label.
symbol, biotypeScalar properties on the :GEN node.
tractability (object)A :Tractability node, linked by a HAS_TRACTABILITY edge.
synonyms (array)Two :Synonym nodes, each linked by its own HAS_SYNONYMS edge.

​
Deduplication in action

If a second gene row also has "tractability": {"id": "T1", ...}, the importer recognizes the matching id and reuses the same :Tractability node instead of creating a duplicate:

In the real OptimusKG dataset, just 19 distinct :Tractability nodes back the 528,500 HAS_TRACTABILITY edges from every gene that references one. The natural key is id when present, falling back to value and then to the full sub-record content.

​
Verify the import

  • Cypher
  • Python SDK
CALL db.labels()
CALL db.edgeTypes()
CALL db.propertyTypes()
print(client.query("CALL db.labels()"))
print(client.query("CALL db.edgeTypes()"))
print(client.query("CALL db.propertyTypes()"))
GMLCreate Graph
githublinkedinyoutubediscord