Temporal Predicates Wire Protocol Reference#

This document provides a comprehensive reference for how temporal predicates serialize to JSON in the GFQL wire protocol. The wire protocol enables interoperability between Python and other systems.

Overview#

The wire protocol uses tagged dictionaries to preserve type information during JSON serialization. This enables:

  • Cross-language compatibility

  • Configuration-driven predicate creation

  • Network transport of queries

  • Storage of predicate definitions

Key Concept: Wire protocol dictionaries can be used directly in the Python API:

# These are equivalent:
pred1 = gt(100)
pred2 = gt(pd.Timestamp("2023-01-01"))
pred3 = gt({"type": "datetime", "value": "2023-01-01T00:00:00", "timezone": "UTC"})

Comparator / Operator Mapping#

  • Python predicate helpers: gt, ge, lt, le, eq, ne

  • Wire predicate types: GT, GE, LT, LE, EQ, NE

  • Same-path chain where JSON operators: gt, ge, lt, le, eq, neq

  • Row-expression comparators (where_rows(expr="...")): =, !=, <>, <, <=, >, >=

WHERE Contexts in Wire JSON#

WHERE-style filtering appears in three distinct wire shapes:

  1. Same-path chain WHERE (where=[...]) uses lower-case operator keys and alias-column references:

{
  "type": "Chain",
  "chain": [
    {"type": "Node", "name": "a"},
    {"type": "Node", "name": "b"}
  ],
  "where": [
    {"ge": {"left": "a.created_at", "right": "b.created_at"}}
  ]
}
  1. Matcher predicates (filter_dict / edge_match) use predicate envelopes (GT/GE/LT/LE/EQ/NE/...) and can carry typed temporal values:

{
  "type": "Node",
  "filter_dict": {
    "created_at": {
      "type": "GT",
      "val": {"type": "datetime", "value": "2024-01-01T00:00:00", "timezone": "UTC"}
    }
  }
}
  1. Row-pipeline WHERE after rows(...) supports either:

    • expression string: {"function": "where_rows", "params": {"expr": "..."} }

    • predicate dict: {"function": "where_rows", "params": {"filter_dict": {...}} }

{
  "type": "Call",
  "function": "where_rows",
  "params": {
    "filter_dict": {
      "created_at": {
        "type": "GE",
        "val": {"type": "datetime", "value": "2024-01-01T00:00:00", "timezone": "UTC"}
      }
    }
  }
}

Explicit Epoch Instant Values#

Do not send a raw integer where a datetime is expected. Numeric predicates on datetime columns raise an incompatible-column-type error rather than silently matching nothing: a bare number does not express an instant, and its unit (seconds or milliseconds) cannot be inferred. To send the epoch counts commonly emitted for datetime columns, wrap them in an explicit datetime value with a required unit:

{
  "type": "Edge",
  "edge_match": {
    "event_time": {
      "type": "GE",
      "val": {
        "type": "datetime",
        "value": 1578528000000,
        "unit": "ms"
      }
    }
  }
}

Rules:

  • unit is required for numeric values and must be "s" (seconds) or "ms" (milliseconds); there is no default unit and no epoch_ms flag.

  • Epoch values denote UTC instants; timezone is not allowed on numeric values. Use an ISO string value to declare a timezone.

  • Counts are integers (integral floats such as 1e3 are accepted and must be exact) within the interoperable JSON integer range of +/- (2^53 - 1); fractional values, booleans, and nonfinite values are rejected.

  • This value form composes with GT, GE, LT, LE, EQ, NE, and Between bounds, and mixed-unit bounds are supported.

  • Serialization normalizes to the typed ISO datetime form in UTC, so round-trips preserve the instant (not the original unit).

  • Typed ISO datetime values remain the preferred portable form.

1. DateTime Comparisons#

Python API#

import pandas as pd
from datetime import datetime
from graphistry import n, e_forward
from graphistry.compute import gt, between

# Using pandas Timestamp
filter1 = n(filter_dict={
    "created_at": gt(pd.Timestamp("2023-01-01 12:00:00"))
})

# Using Python datetime
filter2 = e_forward(edge_match={
    "timestamp": between(
        datetime(2023, 1, 1),
        datetime(2023, 12, 31, 23, 59, 59)
    )
})

Wire Protocol (JSON)#

// GT with datetime
{
    "type": "ASTNode",
    "filter_dict": {
        "created_at": {
            "type": "GT",
            "val": {
                "type": "datetime",
                "value": "2023-01-01T12:00:00",
                "timezone": "UTC"
            }
        }
    }
}

// Between with datetime range
{
    "type": "ASTNode", 
    "edge_match": {
        "timestamp": {
            "type": "Between",
            "lower": {
                "type": "datetime",
                "value": "2023-01-01T00:00:00",
                "timezone": "UTC"
            },
            "upper": {
                "type": "datetime",
                "value": "2023-12-31T23:59:59",
                "timezone": "UTC"
            },
            "inclusive": true
        }
    }
}

Round-trip Example#

# Create predicate
from graphistry.compute import gt
pred = gt(pd.Timestamp("2023-01-01 12:00:00"))

# Serialize to JSON
json_data = pred.to_json()
print(json_data)
# Output: {
#     'type': 'GT',
#     'val': {
#         'type': 'datetime',
#         'value': '2023-01-01T12:00:00',
#         'timezone': 'UTC'
#     }
# }

# Deserialize from JSON
from graphistry.compute.predicates.numeric import GT
pred2 = GT.from_json(json_data)
# pred2 is functionally equivalent to pred

2. Date-Only Comparisons#

Python API#

from datetime import date
from graphistry.compute import eq, ge

# Date equality
filter1 = n(filter_dict={
    "event_date": eq(date(2023, 6, 15))
})

# Date range check
filter2 = n(filter_dict={
    "start_date": ge(date(2023, 1, 1))
})

Wire Protocol (JSON)#

// Date equality
{
    "type": "ASTNode",
    "filter_dict": {
        "event_date": {
            "type": "EQ",
            "val": {
                "type": "date",
                "value": "2023-06-15"
            }
        }
    }
}

// Date greater than or equal
{
    "type": "ASTNode",
    "filter_dict": {
        "start_date": {
            "type": "GE", 
            "val": {
                "type": "date",
                "value": "2023-01-01"
            }
        }
    }
}

3. Time-Only Comparisons#

Python API#

from datetime import time
from graphistry import n, e_forward
from graphistry.compute import is_in, between

# Specific times
filter1 = n(filter_dict={
    "event_time": is_in([
        time(9, 0, 0),
        time(12, 0, 0),
        time(17, 0, 0)
    ])
})

# Time range
filter2 = e_forward(edge_match={
    "daily_schedule": between(
        time(9, 0, 0),
        time(17, 30, 0)
    )
})

Wire Protocol (JSON)#

// IsIn with times
{
    "type": "ASTNode",
    "filter_dict": {
        "event_time": {
            "type": "IsIn",
            "options": [
                {"type": "time", "value": "09:00:00"},
                {"type": "time", "value": "12:00:00"},
                {"type": "time", "value": "17:00:00"}
            ]
        }
    }
}

// Time range
{
    "type": "ASTNode",
    "edge_match": {
        "daily_schedule": {
            "type": "Between",
            "lower": {"type": "time", "value": "09:00:00"},
            "upper": {"type": "time", "value": "17:30:00"},
            "inclusive": true
        }
    }
}

4. Timezone-Aware DateTime#

Python API#

from graphistry.compute import DateTimeValue, gt

# Using timezone-aware timestamp
filter1 = n(filter_dict={
    "timestamp": gt(
        pd.Timestamp("2023-01-01 09:00:00", tz="US/Eastern")
    )
})

# Using DateTimeValue with explicit timezone
dt_val = DateTimeValue("2023-01-01T09:00:00", "US/Eastern")
filter2 = n(filter_dict={
    "timestamp": gt(dt_val)
})

Wire Protocol (JSON)#

// Timezone-aware datetime
{
    "type": "ASTNode",
    "filter_dict": {
        "timestamp": {
            "type": "GT",
            "val": {
                "type": "datetime",
                "value": "2023-01-01T09:00:00",
                "timezone": "US/Eastern"
            }
        }
    }
}

5. Complex Chain with Temporal Predicates#

Python API#

from graphistry import n, e_forward
from graphistry.compute import gt, eq, between
from datetime import datetime, timedelta

# Multi-hop query with temporal filters
chain = g.gfql([
    # Recent transactions
    n(),
    e_forward(edge_match={
        "timestamp": gt(datetime.now() - timedelta(days=7)),
        "amount": gt(1000)
    }),
    # To active accounts
    n(filter_dict={
        "status": eq("active"),
        "last_login": between(
            datetime.now() - timedelta(days=30),
            datetime.now()
        )
    }),
    # Outgoing transfers
    e_forward(edge_match={
        "type": eq("transfer"),
        "timestamp": gt(datetime.now() - timedelta(days=1))
    })
])

Wire Protocol (JSON)#

{
    "type": "Chain",
    "queries": [
        {
            "type": "ASTNode",
            "edge_match": {
                "timestamp": {
                    "type": "GT",
                    "val": {
                        "type": "datetime",
                        "value": "2023-12-18T10:30:00",
                        "timezone": "UTC"
                    }
                },
                "amount": {
                    "type": "GT",
                    "val": 1000
                }
            }
        },
        {
            "type": "ASTNode",
            "filter_dict": {
                "status": {
                    "type": "EQ",
                    "val": "active"
                },
                "last_login": {
                    "type": "Between",
                    "lower": {
                        "type": "datetime",
                        "value": "2023-11-25T10:30:00",
                        "timezone": "UTC"
                    },
                    "upper": {
                        "type": "datetime",
                        "value": "2023-12-25T10:30:00",
                        "timezone": "UTC"
                    },
                    "inclusive": true
                }
            }
        },
        {
            "type": "ASTEdge",
            "direction": "forward",
            "edge_match": {
                "type": {
                    "type": "EQ",
                    "val": "transfer"
                },
                "timestamp": {
                    "type": "GT",
                    "val": {
                        "type": "datetime",
                        "value": "2023-12-24T10:30:00",
                        "timezone": "UTC"
                    }
                }
            }
        }
    ]
}

6. Temporal Value Classes Direct Usage#

Python API#

from graphistry.compute import (
    DateTimeValue, DateValue, TimeValue,
    temporal_value_from_json, gt
)

# Create temporal values
dt_val = DateTimeValue("2023-06-15T14:30:00", "Europe/London")
date_val = DateValue("2023-06-15")
time_val = TimeValue("14:30:00")

# Use in predicates
filter1 = n(filter_dict={"timestamp": gt(dt_val)})
filter2 = n(filter_dict={"event_date": eq(date_val)})
filter3 = n(filter_dict={"daily_time": eq(time_val)})

# Create from JSON
json_dt = {
    "type": "datetime",
    "value": "2023-06-15T14:30:00",
    "timezone": "Europe/London"
}
dt_from_json = temporal_value_from_json(json_dt)

Wire Protocol (JSON)#

// DateTimeValue serialization
{
    "type": "datetime",
    "value": "2023-06-15T14:30:00",
    "timezone": "Europe/London"
}

// DateValue serialization
{
    "type": "date",
    "value": "2023-06-15"
}

// TimeValue serialization
{
    "type": "time",
    "value": "14:30:00"
}

7. Full Round-Trip Example#

# 1. Create a complex query with temporal predicates
from graphistry import n, Chain
from graphistry.compute import gt, between, is_in
from datetime import datetime, date, time
import pandas as pd

query = Chain([
    n(filter_dict={
        "created": gt(pd.Timestamp("2023-01-01")),
        "event_date": between(date(2023, 6, 1), date(2023, 6, 30)),
        "event_time": is_in([time(9, 0), time(12, 0), time(17, 0)])
    })
])

# 2. Serialize to JSON
json_query = query.to_json()
print(json_query)

# 3. Send over wire (simulated)
import json
wire_data = json.dumps(json_query)
received_data = json.loads(wire_data)

# 4. Deserialize on receiving end
from graphistry import Chain
reconstructed_query = Chain.from_json(received_data)

# 5. Apply to graph data
result = g.gfql(reconstructed_query.chain)

Wire Protocol Structure#

Temporal Value Types#

All temporal values in the wire protocol follow this pattern:

// DateTime with timezone
interface DateTimeWire {
    type: "datetime";
    value: string;      // ISO 8601 format
    timezone?: string;  // IANA timezone (default: "UTC")
}

// Date only
interface DateWire {
    type: "date";
    value: string;      // YYYY-MM-DD format
}

// Time only  
interface TimeWire {
    type: "time";
    value: string;      // HH:MM:SS[.ffffff] format
}

Predicate Structure#

Predicates containing temporal values serialize as:

interface TemporalPredicate {
    type: "GT" | "LT" | "GE" | "LE" | "EQ" | "NE";
    val: DateTimeWire | DateWire | TimeWire;
}

interface BetweenPredicate {
    type: "Between";
    lower: DateTimeWire | DateWire | TimeWire;
    upper: DateTimeWire | DateWire | TimeWire;
    inclusive: boolean;
}

interface IsInPredicate {
    type: "IsIn";
    options: Array<DateTimeWire | DateWire | TimeWire>;
}

Key Points#

  1. Type Safety: Raw strings are rejected in the Python API to avoid ambiguity

  2. Automatic Conversion: Python datetime objects are automatically converted to appropriate temporal values

  3. Timezone Preservation: Timezone information is preserved through serialization

  4. Tagged Format: JSON uses tagged dictionaries to preserve type information

  5. Direct Usage: Wire protocol dicts can be passed directly to Python predicates

Error Handling#

# Raw strings raise ValueError
try:
    filter_raw = n(filter_dict={"date": gt("2023-01-01")})
except ValueError as e:
    print(e)
    # Output: Raw string '2023-01-01' is ambiguous. Use:
    #   - gt(pd.Timestamp('2023-01-01')) for datetime
    #   - gt({'type': 'datetime', 'value': '2023-01-01T00:00:00'}) for explicit type

# Valid approaches
filter1 = n(filter_dict={"date": gt(pd.Timestamp("2023-01-01"))})
filter2 = n(filter_dict={"date": gt(datetime(2023, 1, 1))})
filter3 = n(filter_dict={"date": gt({"type": "datetime", "value": "2023-01-01T00:00:00", "timezone": "UTC"})})

Performance Considerations#

  • Temporal predicates use pandas’ optimized datetime operations

  • Timezone conversions are handled efficiently

  • For large datasets, ensure datetime columns are properly typed (not object dtype)

  • Use pd.Timestamp for best performance when creating many predicates programmatically