tools/generate_parquet_maml.sh (bundled with this library) is a generic tool any project
depending on parquet-fortran can reuse to embed its own .maml schemas directly into compiled
Fortran source, so a downstream pipeline doesn't need to locate/ship .maml files at run time. To
do this in your own project:
tools/generate_parquet_maml.sh into your own project (e.g. under your own tools/). It
is part of the published package, so if you consume this library as an fpm dependency the script
arrives inside the resolved dependency tree — under build/dependencies/parquet-fortran/tools/
— rather than in your own repository..maml schema files under a schemas/ directory at your project's root — this is
parquet-fortran's own convention and the default the script looks for, but not required: pass
--dir=<name> (or --dir <name>) to use a different directory name if your project already has
its own convention. That directory is scanned recursively, so you are free to group schemas
in subdirectories; every .maml filename must be unique across the whole tree, because a schema
is addressed by its filename rather than by its path.tools/generate_parquet_maml.sh (or
tools/generate_parquet_maml.sh --dir=<name> for a non-default directory) — this writes
src/parquet_maml.f90 in your project, generated from your .maml files. Commit that
file. Like the generated table types, it is regenerated on demand rather than at build time,
which is what keeps your own build free of any dependency on Python or on this script; re-run
the generator whenever a .maml changes, and commit the result alongside it.use parquet_maml (the module the script just generated for you) alongside use parquet in
your code — the second is what supplies the parquet_schema type itself.
get_parquet_maml("your_schema.maml") returns that schema already parsed, so there is no
parquet_parse_maml call of your own to make; set_maml(maml_default, [maml_file]) resolves
either to the embedded default or, when maml_file names a file on disk, to that file validated
against the default. Square brackets mark an optional argument.Two files: one schema and one program. The schema goes under schemas/; note that the fields:
list entries begin at column 0, which is what the format expects — see
The MAML metadata format.
# schemas/mysurvey.maml
dataset: probe_dataset
table: mysurvey
fields:
- name: objid
data_type: int64
info: object identifier
- name: ra
data_type: float64
unit: deg
- name: flux
data_type: float32
unit: nJy
After tools/generate_parquet_maml.sh has written src/parquet_maml.f90:
program embedded_schema_quickstart
use parquet
use parquet_maml, only : get_parquet_maml ! the module the script generated
use iso_fortran_env, only : int64, real64, real32
implicit none
type(parquet_schema) :: schema
type(parquet_writer) :: w
!
schema = get_parquet_maml("mysurvey.maml") ! or "mysurvey" -- both match
print '(a,l1,a,i0)', "parsed=", schema%is_parsed(), " fields=", schema%get_num_fields()
!
call parquet_open_writer(w, "mysurvey.parquet", schema)
call parquet_write_column(w, "objid", [1_int64, 2_int64, 3_int64])
call parquet_write_column(w, "ra", [1.0_real64, 2.0_real64, 3.0_real64])
call parquet_write_column(w, "flux", [1.0_real32, 2.0_real32, 3.0_real32])
call parquet_close_writer(w)
end program embedded_schema_quickstart
prints parsed=T fields=3 and writes the file. The schema arrives ready to use: nothing on this
path reads a .maml file at run time, and there is no separate parse step.
get_parquet_maml matches a schema by its filename, with or without the .maml extension. One
held in a subdirectory answers to its full relative path as well, so all three of these reach the
same schema for schemas/dr2/extra.maml:
schema = get_parquet_maml("dr2/extra.maml")
schema = get_parquet_maml("extra.maml")
schema = get_parquet_maml("extra")
That is why filenames have to be unique across the scanned tree: subdirectories organise your schemas without changing how any of them is addressed.
Alongside get_parquet_maml, the generated module makes one accessor public per schema, named
after the file — parquet_maml_extra() for extra.maml. These take no argument, and are worth
using where you would rather the compiler than a string caught a typo.
set_mamlset_maml(maml_default, [maml_file]) is for a pipeline that ships a default schema and lets its
user supply a replacement:
maml_file absent, or a blank string, it returns the embedded maml_default schema —
exactly what get_parquet_maml(maml_default) would give you;maml_file naming a file on disk, it loads that file, validates it against the embedded
default, and returns it parsed.The second form does more than load the user's file, and the difference is the reason to use it.
The schema you get back carries the default's full field list, not the user's: a field the
user's MAML omits is still there, reported by schema%is_column_set(name) as .false. and
recorded in schema%maml%missing_columns. A reader built from that schema therefore knows which
columns to skip, rather than simply never mentioning them. schema%maml%name is the user's file
path, so a diagnostic can say which schema was actually in force.
--checkBecause src/parquet_maml.f90 is committed rather than generated at build time, it can drift from
the .maml files it came from. --check regenerates in memory and compares, writing nothing:
tools/generate_parquet_maml.sh --check
It exits 0 when the committed module is current and 1 when it is stale, naming the file and saying
to re-run the generator — which makes it a one-line addition to your own CI. Pass it whatever
--dir/--module options the generating run used; with different options it is comparing against
a different file.
--dir=<name> selects the directory to scan (default schemas/), and --module=<name> names the
generated module, and therefore the file it is written to, src/<name>.f90 (default
parquet_maml). The module name is the reason to reach for the second: some projects require every
module of their own to carry their package's prefix, and one embedded-schema module per project is
otherwise all you can have — a second run overwrites the first, since both the module name and the
output path come from it.
tools/generate_parquet_maml.sh --dir=metadata --module=mypipeline_maml
writes src/mypipeline_maml.f90, holding a module named mypipeline_maml, from every .maml
under metadata/.
Each of these is a message and a nonzero exit, before anything is written:
.maml files under the scanned directory, including a directory that does not exist —
No .maml files found under <dir>/.--module name. A Fortran module name is letters, digits and underscores,
starting with a letter, and at most 63 characters.At run time, get_parquet_maml error stops on a name it does not have, with
get_parquet_maml: unknown internal MAML file: <name>.
The generated parquet_maml module imports four names from parquet — parquet_schema,
parquet_load_maml_file, parquet_parse_maml and parquet_validate_user_maml — and does not
import parquet_maml_base at all. parquet_maml_base is where this library's own bundled
schemas are embedded, so importing it gives you those fixtures and the shared parquet_maml_file
type, never your project's own schemas; your generated parquet_maml is what holds those. (If
you're contributing to parquet-fortran itself and need to regenerate its own built-in schema
module, see CONTRIBUTING.md.)
A second, related generator ships alongside it:
tools/generate_user_table_code.py turns a MAML schema into a named
parquet_table extension type with one accessor per column, for programs that always read the same
columns. It follows the same copy-it-into-your-project, commit-the-output convention as this one.