Creating Records
PormG provides methods for inserting data into your database, from single rows to complex related objects. All create operations are async-aware and will automatically participate in a transaction context if one is active.
Single Record Creation
Use the .create() method to insert individual records. It returns a Dict containing the newly created record, including any database-generated fields (like auto-increment primary keys).
# Load your models
include("db/models.jl")
import .models as M
# Create a new record
new_record = M.Just_a_test_deletion.objects.create("name" => "test", "test_result" => 1)Return Value
The return value is a Dict{Symbol, Any} with all fields of the inserted record:
Dict{Symbol, Any} with 4 entries:
:id => 172 # Auto-generated primary key
:name => "test"
:test_result => 1
:test_result2 => missing # Null/unset fields show as missingYou can access returned values immediately:
new_record = M.Driver.objects.create(
"forename" => "Lewis",
"surname" => "Hamilton",
"nationality" => "British",
"driverref" => "hamilton",
"dob" => Date(1985, 1, 7)
)
# Access the auto-generated ID
driver_id = new_record[:driverid]
# Use it in subsequent operations
race_result = M.Result.objects.create(
"driverid" => driver_id,
"raceid" => 1,
"constructorid" => 1,
"positionorder" => 1
)Validation
PormG validates that all non-nullable fields are provided. If a required field is missing, it will raise an ArgumentError.
# This will fail because the 'driverref' field is required (NOT NULL)
driver = M.Driver.objects.create(
"forename" => "Lewis",
"surname" => "Hamilton",
"nationality" => "British",
"dob" => Date(1985, 1, 7)
# Missing: driverref
)Error:
ERROR: ArgumentError: Error in insert, the field driverref not allow nullDefault Values
Fields with default values don't need to be specified:
# If 'created_at' has a default like CURRENT_TIMESTAMP, you can omit it
record = M.Status.objects.create("status" => "Finished")
# created_at will be set automatically to the current timestampGenerated Fields
Primary key fields with GENERATED ALWAYS AS IDENTITY or auto-increment are created automatically:
# Don't provide an ID—the database generates it
record = M.Driver.objects.create(
"forename" => "Max",
"surname" => "Verstappen",
"nationality" => "Dutch",
"driverref" => "max_verstappen",
"dob" => Date(1997, 4, 1)
)
# The returned dict includes the generated ID
generated_id = record[:driverid]Creating with Relationships
To create a record with a ForeignKey relationship, you need the ID of the related record. You can either:
- Create the related record first and use its returned ID
- Use an existing ID from the database
Example: Race with Circuit
# Step 1: Create the related record (Circuit)
circuit = M.Circuit.objects.create(
"name" => "Monaco",
"country" => "Monaco",
"circuitref" => "monaco",
"location" => "Monte Carlo",
"lat" => 43.7347,
"lng" => 7.4206,
"alt" => 5,
"url" => "https://en.wikipedia.org/wiki/Circuit_de_Monaco"
)
# Step 2: Extract the auto-generated circuit ID
circuit_id = circuit[:circuitid]
# Step 3: Create the dependent record using the foreign key
race = M.Race.objects.create(
"year" => 2024,
"round" => 8,
"circuitid" => circuit_id, # Reference the related record
"name" => "Monaco Grand Prix",
"date" => Date(2024, 5, 26),
"time" => Time(13, 0, 0),
"url" => "https://www.formula1.com/races/2024-monaco-gp"
)Example: Result with Driver, Constructor, and Race
A more complex example showing multiple relationships:
# Assume we have IDs from existing or newly created records
driver_id = M.Driver.objects.filter("forename" => "Lewis") |> list |> first |> x -> x[:driverid]
constructor_id = M.Constructor.objects.filter("name" => "Mercedes") |> list |> first |> x -> x[:constructorid]
race_id = M.Race.objects.filter("year" => 2024, "round" => 1) |> list |> first |> x -> x[:raceid]
# Create the result linking all three
result = M.Result.objects.create(
"raceid" => race_id,
"driverid" => driver_id,
"constructorid" => constructor_id,
"positionorder" => 1,
"points" => 25.0,
"grid" => 1,
"laps" => 58
)
# The returned record includes all fields and IDs
@info "Result created" result_id=result[:resultid] points=result[:points]Foreign Key Constraints
PormG enforces referential integrity. Attempting to create a record with a non-existent foreign key will raise an error:
# This will fail if circuit_id=9999 doesn't exist
try
race = M.Race.objects.create(
"year" => 2025,
"round" => 1,
"circuitid" => 9999, # Does not exist!
"name" => "Phantom Grand Prix",
"date" => Date(2025, 3, 23)
)
catch e
@error "Foreign key constraint violation" exception=e
endCreating Multiple Records Individually
While loop-based creation is possible, for large datasets you should prefer Bulk Operations. Single create() calls are appropriate for:
- User-submitted form data in web requests
- Real-time operations that need immediate feedback
- Small, on-demand data entry
For loading CSV files or batch imports, use bulk_insert() or bulk_copy().
Loop Example
# Good for small, interactive operations
test_data = [
("test10", 10),
("test11", 11),
("test12", 12)
]
created_ids = []
for (name, test_result) in test_data
record = M.Just_a_test_deletion.objects.create(
"name" => name,
"test_result" => test_result
)
push!(created_ids, record[:id])
end
@info "Created $(length(created_ids)) records" ids=created_idsWhy Not Loop for Large Datasets?
# ❌ SLOW: 10,000 individual inserts = 10,000 database round-trips
for i in 1:10000
M.Driver.objects.create("forename" => "Driver $i", ...)
end
# ✅ FAST: One bulk operation = 1-10 database round-trips
df = DataFrame(forename = ["Driver $i" for i in 1:10000], ...)
bulk_insert(M.Driver.objects, df)See Bulk Operations for performance comparisons and when to use each method.