ORLite(3pm)
β¨ NAME β¨
okay so this is the manual page for ORLite(3pm) β from the User Contributed Perl Documentation β and it's ORLite: the Extremely light weight SQLite-specific ORM. no cap, that's the whole name. π
π SYNOPSIS π
bestie, here's the vibe in code. first the simplest possible usage, then literally every option turned on or specified. some options shown are mutually exclusive, so this code would not actually run β the comments say it, I'm just repeating it with love. π«Ά
package Foo;
# Simplest possible usage
use strict;
use ORLite 'data/sqlite.db';
my @awesome = Foo::Person->select(
'where first_name = ?',
'Adam',
);
package Bar;
# All available options enabled or specified.
# Some options shown are mutually exclusive,
# this code would not actually run.
use ORLite {
package => 'My::ORM',
file => 'data/sqlite.db',
user_version => 12,
readonly => 1,
create => sub {
my $dbh = shift;
$dbh->do('CREATE TABLE foo ( bar TEXT NOT NULL )');
},
tables => [ 'table1', 'table2' ],
cleanup => 'VACUUM',
prune => 1,
};
π DESCRIPTION π
SQLite is literally this light single-file SQL database, and it's giving an excellent platform for embedded storage of structured data. π¦
But bestie, even though it looks superficially similar to a regular server-side SQL database, SQLite has some significant attributes that make using it like a traditional database lowkey difficult. π
For example, SQLite is extremely fast to connect to compared to server databases β 1000 connections per second is not unknown β and it is particularly bad at concurrency, because it can only lock transactions at a database-wide level. that's just the way it is. π
This role as a superfast internal data store can clash with the roles and designs of traditional object-relational modules like Class::DBI or DBIx::Class. not me seeing the drama between two entire paradigms. π
What this situation would seem to need is an object-relation system that is designed specifically for SQLite and is aligned with its idiosyncracies. periodt.
ORLite is an object-relation system specifically tailored for SQLite that follows many of the same principles as the ::Tiny series of modules, and has a design and feature set that aligns directly to the capabilities of SQLite. it's giving exactly what SQLite ordered. β¨
Further documentation will be available at a later time, but the synopsis gives a pretty good idea of how it works. iykyk. π
π¦ How ORLite Works π¦
ORLite discovers the schema of a SQLite database, and then generates the code for a complete set of classes that let you work with the objects stored in that database. iconic behaviour, honestly. π
In the simplest form, your target root package "uses" ORLite, which will do the schema discovery and code generation at compile-time. no extra steps, no mysterious rituals. π§
When called, ORLite generates two types of packages.
- Firstly, it builds database connectivity, transaction support, and other purely database level functionality into your root namespace. πΎ
- Secondly, it will create one sub-package underneath the namespace of the root module for each table or view it finds in the database. πΈ
Once the basic table support has been generated, it will also try to load an "overlay" module of the same name. Thus, by creating a Foo::TableName module on disk containing "extra" code, you can extend the original and add additional functionality to it. slay. π
β¨ OPTIONS β¨
okay bestie, so here's how ORLite takes its options: you hand them over as a HASH parameter on the use line, and it reads them at compile time. πΈ
And for a lil convenience treat: you can also just pass the name of an existing SQLite file to load, and ORLite will apply defaults to literally every other option. π«Ά
# The following are equivalent
use ORLite $filename;
use ORLite {
file => $filename,
};
Here's the tea on every single option, one by one: π
π package
This optional parameter gives ORLite the Perl root namespace it should generate the code for. And no, this class does not have to exist as a module on disk, and it doesn't need anything loaded into it or sitting in the namespace either. It's giving ghost class, and that's fine. π
By default, the package used is whatever package called ORLite's import method β which, realistically, is the one from your use ORLite { ... } line.
π file
The compulsory parameter. Like, the only compulsory one. π€ It gives the path to the SQLite file that ORLite should use for the ORM class tree.
If the file already exists, it's gotta be a valid SQLite file match that's supported by the version of DBD::SQLite installed on your system. No knockoffs, no half-files. β
ORLite will throw an exception if the file does not exist β unless you also pass the create option to signal that ORLite should make a brand new SQLite file on demand.
If the create option is provided, the path you gave has to be creatable. And when it's creating the database, ORLite will also create any missing directories it needs along the way. Iconic, honestly. π
π user_version
Real talk: when you're working with ORLite, the biggest risk to the stability of your code is usually the reliability of the SQLite schema structure over time. π
When the database schema changes, the code ORLite generates changes too. And that can easily result in an unexpected change in the API of your class tree, which then breaks all the code sitting on top of those generated APIs. Not the vibe.
So to fix this, ORLite supports a feature called schema version-locking. π
Via the user_version SQLite pragma, you can set a revision for your database schema, bumping the number each time you make a non-trivial change to your schema.
SQLite> PRAGMA user_version = 7
When you're creating your ORLite package, you should specify this schema version number via the user_version option:
use ORLite {
file => $filename,
user_version => 7,
};
Then, when connecting to the SQLite database, the user_version you provided gets checked against the version in the schema. If they don't match, that means the schema has unexpectedly changed, and the code ORLite generates would be different from the expected API. π
Rather than risk potentially destructive errors from that changing code, ORLite will simply refuse to run and throw an exception.
So using user_version lets you write code against a SQLite database with high certainty that it'll keep working. Or, at the very least, that if the SQLite schema does change in the future, your code will fail quickly and safely instead of running away and causing unknown behaviour. Safe girl summer. π¦
By default, the user_version option is false and the value of the SQLite PRAGMA user_version will not be checked.
π readonly
To conserve memory and reduce complexity, ORLite generates the API differently depending on how writable the SQLite database is. β¨
Features like transaction support, plus methods that result in INSERT, UPDATE and DELETE queries, will only be added if they can actually be run β which means you get an immediate "no such method" exception at the Perl level, instead of letting your application do a bunch more work only to slam into an inevitable SQLite error. π
By default, the readonly option is based on the filesystem permissions of the SQLite database β which matches SQLite's own writability behaviour. π
But you can pass readonly explicitly if you want. Generally you'd do that when you're working with a read-write database but you only plan to read from it.
Forcing readonly to true will halve the size of the code generated to produce your ORM, and it reduces the size of any auto-generated API documentation using ORLite::Pod by a similar amount. Shrinkage, but make it fashion. π
It also makes sure this process will only take shared read locks on the database β preventing any chance of creating a dead-lock on the SQLite database. No deadlocks, no drama. π₯
π create
The create option expands ORLite beyond just consuming other people's databases, letting it produce and operate on databases under the direct control of your code. Main character energy, fr fr. π
create supports two alternative forms:
- If
createis set to a simple true value, an empty SQLite file will be created if the location given in thefileoption does not exist. - If
createis set to aCODEreference, that function will be executed on the new database before ORLite attempts to scan the schema.
The CODE reference gets passed a plain DBI connection handle, which you should operate on normally. Just note that because create fires before the code generation phase, none of the functionality produced by the generated classes is available while your create code is running. π
The use of thecreateoption is incompatible with thereadonlyoption. Pick a lane bestie.
π tables
The tables option should be a reference to an array containing a list of table names. For large or complex SQLite databases where you only need a fraction of the schema, limiting the set of tables reduces both the startup time needed to scan the structure of the SQLite schema and the memory cost of the class tree. Efficiency? Obsessed. β
If the tables option isn't provided, ORLite will try to produce a class for every table in the main schema that isn't prefixed with sqlite_.
π cache
use ORLite {
file => 'dbi:SQLite:sqlite.db',
user_version => 2,
cache => 'cache/directory',
};
The cache option cuts down the time needed to scan the SQLite database table structures and generate code for them β by saving the generated code to a cache directory and loading from that file instead of generating everything from scratch every single time. π§
π cleanup
When you're working with embedded SQLite databases that hold rapidly changing state data, it's genuinely important for database performance and general health to VACUUM or ANALYZE the database regularly. Take care of her. πΈ
The cleanup option should be a single literal SQL statement.
If provided, that statement will automatically be run on the database during END-time, after the last transaction has been completed.
Typically this will be either a full VACUUM; ANALYZE or the more simple VACUUM.
π prune
In some situations β like during test scripts β an application only needs the created SQLite database temporarily. In those situations you can pass the prune option to instruct ORLite to delete the SQLite database when the program ends. ποΈ
If any directories were made in order to create the SQLite file, those directories will be cleaned up and removed as well.
Ifpruneis enabled, you generally should not usecleanupβ any cleanup operation will be made pointless whenprunedeletes the file. π
By default, the prune option is set to false.
π shim
In some situations you might want to make extensive changes to the behaviour of the classes and methods ORLite generates. Under normal circumstances all the code is generated directly into the table class, which can make overriding a method difficult. π
The shim option makes ORLite generate all of its methods into a separate Foo::TableName::Shim class, and leaves the main table class Foo::TableName as a transparent subclass of the shim. Cute layering, no cap. π
This lets you alter the behaviour of a table class without having to do nasty tricks with symbol tables in order to alter or replace methods.
package My::Person;
# Write a log message when we create a new object
sub create {
my $class = shift;
my $self = SUPER::create(@_);
my $name = $self->name;
print LOG "Created new person '$name'\n";
return $self;
}
The shim option is global β it alters the structure of all table classes at once. But unless you're actually making alterations to a class, the impact of this different class structure should be zero. β¨
π unicode
You can use this option to tell ORLite that your database uses unicode. π
At the moment, it just enables the sqlite_unicode option while connecting to your database. There'll be more in the future. π
π ROOT PACKAGE METHODS
okay so literally every ORLite root package gets the same identical set of methods, no cap. they're the ones that control connections to the database, transactions, and firing off queries of every type at the database. same deal for all of them π
in any examples you see, the example root package Foo::Bar is used.
all methods are static. ORLite does not allow the creation of a Foo::Bar object (although you may wish to add this capability yourself). π
dsn
my $string = Foo::Bar->dsn;
the dsn accessor hands you back the dbi connection string used to connect to the SQLite database, as a string. that's it, that's the whole vibe π
dbh
my $handle = Foo::Bar->dbh;
okay bestie, listen up, this one has lore. to reliably prevent potential SQLite deadlocks resulting from multiple connections in a single process, each ORLite package will only ever maintain a single connection to the database.
during a transaction, this will be the same (cached) database handle.
although in most situations you should not need a direct DBI connection handle, the dbh method provides a method for getting a direct connection in a way that is compatible with ORLite's connection management.
please note that these connections should be short-lived, you should never hold onto a connection beyond the immediate scope. π
the transaction system in ORLite is specifically designed so that code using the database should never have to know whether or not it is in a transaction.
because of this, you should never call the ->disconnect method on the database handles yourself, as the handle may be that of a currently running transaction.
further, you should do your own transaction management on a handle provided by the <dbh> method.
in cases where there are extreme needs, and you absolutely have to violate these connection handling rules, you should create your own completely manual DBI->connect call to the database, using the connect string provided by the dsn method.
the dbh method returns a DBI::db object, or throws an exception on error.
connect
my $dbh = Foo::Bar->connect;
the connect method exists for the (extremely rare) situation in which you need a raw connection to the database, evading the normal tracking and management provided of the ORM.
the use of raw connections in this manner is strongly discouraged, as you can create fatal deadlocks in SQLite if either the core ORM or the raw connection uses a transaction at any time. π
to summarise, do not use this method unless you REALLY know what you are doing.
YOU HAVE BEEN WARNED! π¨
connected
my $active = Foo::Bar->connected;
the connected method provides introspection of the connection status of the library. it returns true if there is any connection or transaction open to the database, or false otherwise. vibe check for your db π¦
begin
Foo::Bar->begin;
the begin method indicates the start of a transaction.
in the same way that ORLite allows only a single connection, likewise it allows only a single application-wide transaction.
no indication is given as to whether you are currently in a transaction or not, all code should be written neutrally so that it works either way or doesn't need to care.
returns true or throws an exception on error.
while transaction support is always built for every ORLite-generated class tree, if the database is opened "readonly" thecommitmethod will not exist at all in the API, and your only way of ending the transaction (and the resulting persistent connection) will berollback. π
commit
Foo::Bar->commit;
the commit method commits the current transaction. if called outside of a current transaction, it is accepted and treated as a null operation.
once the commit has been completed, the database connection falls back into auto-commit state. if you wish to immediately start another transaction, you will need to issue a separate ->begin call.
returns true or throws an exception on error.
commit_begin
Foo::Bar->begin;
# Code for the first transaction...
Foo::Bar->commit_begin;
# Code for the last transaction...
Foo::Bar->commit;
by default, ORLite-generated code uses opportunistic connections.
every <select> you call results in a fresh DBI connect, and a disconnect occurs after query processing and before the data is returned. connections are only held open indefinitely during a transaction, with an immediate disconnect after your commit.
this makes ORLite very easy to use in an ad-hoc manner, but can have performance implications. π
while SQLite itself can handle 1000 connections per second, the repeated destruction and repopulation of SQLite's data page caches between your statements (or between transactions) can slow things down dramatically. π
the commit_begin method is used to commit the current transaction and immediately start a new transaction, without disconnecting from the database.
its exception behaviour and return value is identical to that of a plain commit call.
rollback
the rollback method rolls back the current transaction. if called outside of a current transaction, it is accepted and treated as a null operation.
once the rollback has been completed, the database connection falls back into auto-commit state. if you wish to immediately start another transaction, you will need to issue a separate ->begin call.
if a transaction exists at END-time as the process exits, it will be automatically rolled back. π§
returns true or throws an exception on error.
rollback_begin
Foo::Bar->begin;
# Code for the first transaction...
Foo::Bar->rollback_begin;
# Code for the last transaction...
Foo::Bar->commit;
by default, ORLite-generated code uses opportunistic connections.
every <select> you call results in a fresh DBI connect, and a disconnect occurs after query processing and before the data is returned. connections are only held open indefinitely during a transaction, with an immediate disconnect after your commit.
this makes ORLite very easy to use in an ad-hoc manner, but can have performance implications. π
while SQLite itself can handle 1000 connections per second, the repeated destruction and repopulation of SQLite's data page caches between your statements (or between transactions) can slow things down dramatically. π
the rollback_begin method is used to rollback the current transaction and immediately start a new transaction, without disconnecting from the database.
its exception behaviour and return value is identical to that of a plain commit call.
do
Foo::Bar->do(
'insert into table (foo, bar) values (?, ?)',
{},
$foo_value,
$bar_value,
);
the do method is a direct wrapper around the equivalent DBI method, but applied to the appropriate locally-provided connection or transaction. β¨
it takes the same parameters and has the same return values and error behaviour.
selectall_arrayref
the selectall_arrayref method is a direct wrapper around the equivalent DBI method, but applied to the appropriate locally-provided connection or transaction.
it takes the same parameters and has the same return values and error behaviour.
selectall_hashref
the selectall_hashref method is a direct wrapper around the equivalent DBI method, but applied to the appropriate locally-provided connection or transaction.
it takes the same parameters and has the same return values and error behaviour.
selectcol_arrayref
the selectcol_arrayref method is a direct wrapper around the equivalent DBI method, but applied to the appropriate locally-provided connection or transaction.
it takes the same parameters and has the same return values and error behaviour.
selectrow_array
the selectrow_array method is a direct wrapper around the equivalent DBI method, but applied to the appropriate locally-provided connection or transaction.
it takes the same parameters and has the same return values and error behaviour.
selectrow_arrayref
the selectrow_arrayref method is a direct wrapper around the equivalent DBI method, but applied to the appropriate locally-provided connection or transaction.
it takes the same parameters and has the same return values and error behaviour.
selectrow_hashref
the selectrow_hashref method is a direct wrapper around the equivalent DBI method, but applied to the appropriate locally-provided connection or transaction.
it takes the same parameters and has the same return values and error behaviour.
prepare
the prepare method is a direct wrapper around the equivalent DBI method, but applied to the appropriate locally-provided connection or transaction
it takes the same parameters and has the same return values and error behaviour.
in general though, you should try to avoid the use of your own prepared statements if possible, although this is only a recommendation and by no means prohibited. π«Ά
pragma
# Get the user_version for the schema
my $version = Foo::Bar->pragma('user_version');
the pragma method provides a convenient method for fetching a pragma for a database. see the SQLite documentation for more details. π
β¨ TABLE PACKAGE METHODS β¨
okay so here's the cute part π when you use ORLite, all your database tables pop into existence as objects, and they get named in a camel-cased kinda way. so like, if your model name is Foo::Bar...
use ORLite {
package => 'Foo::Bar',
file => 'data/sqlite.db',
};
...then a table named user would be accessed as Foo::Bar::User, while a table named user_data would become Foo::Bar::UserData. naming conventions ate, fr fr π
base
my $namespace = Foo::Bar::User->base; # Returns 'Foo::Bar'
normally bestie you'll only ever work directly with a table class, and only with one ORLite package. that's the vibe. π
but if for some reason you gotta juggle multiple ORLite packages at the same time and you don't wanna hardcode the root namespace over and over (so extra π), you can pull the root namespace straight out of an object or table class with the base method. easy.
table
print Foo::Bar::UserData->table; # 'user_data'
You shouldn't need the table's name for any simple operations, but every now and then you may need it programmatically. And if you do need it, the table method hands you the table name. that's it, that's the method β¨
table_info
# List the columns in the underlying table
my $columns = Foo::Bar::User->table_info;
foreach my $c ( @$columns ) {
print "Column $c->{name} $c->{type}";
print " not null" if $c->{notnull};
print " default $c->{dflt_value}" if defined $c->{dflt_value};
print " primary key" if $c->{pk};
print "\n";
}
The table_info method is a lil wrapper around the SQLite table_info pragma, and it gives you simplified access to the column metadata for the underlying table β y'know, for those advanced functions that need to peek directly at the column list. π
Returns a reference to an ARRAY containing a list of columns, where each column is a reference to a HASH with the keys cid, dflt_value, name, notnull, pk and type. manifesting that hash structure for u π¦
new
my $user = Foo::Bar::User->new(
name => 'Your Name',
age => 23,
);
The new constructor makes an anonymous object β it does not read from or write to the database. And it also won't do any kind of validation, because ORLite is built for embedded databases and just kinda presumes you know what you're doing. main character energy, honestly π
insert
my $user = Foo::Bar::User->new(
name => 'Your Name',
age => 23,
)->insert;
The insert method takes an existing anonymous object and inserts it into the database, then returns the object back as a lil convenience. π«Ά
It's the second half of the slower manual two-phase object construction process. (iykyk)
If the table has an auto-incrementing primary key β and you haven't provided a value for it yourself β the identifier for the new record gets fetched back from the database and set in your object. automatic, no cap β¨
my $object = Foo::Bar::User->new( name => 'Foo' )->insert;
print "Created new user with id " . $user->id . "\n";
create
my $user = Foo::Bar::User->create(
name => 'Your Name',
age => 23,
);
The new + insert combo is great when you need to do interesting constructor mechanisms, but likeβ¦ most of the time you already have all the attributes ready and you just wanna create and insert the record in a single step. one step, periodt π
The create method is that shorthand, and it's just the functional equivalent of this:
sub create {
shift->new(@_)->insert;
}
It returns the newly created object after it has been inserted. π
load
my $user = Foo::Bar::User->load( $id );
If your table has a single column primary key, a load method will be generated in the class. If there's no primary key, the method just... isn't created. no key, no method, that's the rule π§
The load method is a shortcut for fetching a single object based on the value of the primary key. But bestie, β οΈ it should only be used for cases where your code trusts that the record already exists.
It returns a Foo::Bar::User object, or throws an exception if the object does not exist. π
id
The id accessor is a convenience method that gets added to your table class to make your code more readable, whenever ORLite detects certain patterns of column naming. pattern recognition queen π
For example, take this definition, where the convention is that all primary keys are the table name followed by _id:
create table foo_bar (
foo_bar_id integer not null primary key,
name string not null,
)
When ORLite detects this pattern β and as long as the table doesn't have an id column β the extra id accessor gets added to your class, making these two expressions equivalent in both function and performance. π
my $foo_bar = My::FooBar->create( name => 'Hello' );
# Column name accessor
$foo_bar->foo_bar_id;
# Convenience id accessor
$foo_bar->id;
See how the second one involves way less repetition and reads so much more cleanly? we love less typing β¨
select
my @users = Foo::Bar::User->select;
my $users = Foo::Bar::User->select( 'where name = ?', @args );
The select method is how you retrieve objects from the database. π
In list context, it returns an array with all the matching elements. In scalar context, it returns an array reference with that same data.
You can filter the results or order them by passing SQL code to the method:
my @users = DB::User->select( 'where name = ?', $name );
my $users = DB::User->select( 'order by name' );
Because select provides only the thinnest of layers around pure SQL (it literally just generates the SELECT ... FROM table_name), you're free to use anything you want in your query, including subselects and function calls. go off. π₯
If you call it with no arguments at all, it returns all rows of the table in SQLite's natural sort order.
iterate
Foo::Bar::User->iterate( sub {
print $_->name . "\n";
} );
The iterate method lets you process big tables one record at a time without having to load them all into memory in advance. O(1) memory said what π
This plays right into SQLite's strengths, letting it do the work of loading an arbitrarily large stream of records from disk, while keeping the full power of Perl for processing those records. iconic combo honestly.
The last argument to iterate must be a subroutine reference that will be called for each element in the list, with the object provided in the topic variable $_.
So the iterate snippet above is functionally equivalent to this next one β except it costs O(1) memory instead of O(n). π¦
foreach ( Foo::Bar::User->select ) {
print $_->name . "\n";
}
You can filter the list via SQL in exactly the same way you can with select.
Foo::Bar::User->iterate(
'order by ?', 'name',
sub {
print $_->name . "\n";
}
);
You can also use it in raw form from the root namespace for better control. Using this form also lets you write arbitrarily complex queries, including joins. But heads up: instead of being objects, rows are provided as ARRAY references when you use it this way. β
Foo::Bar->iterate(
'select name from user order by name',
sub {
print $_->[0] . "\n";
}
);
count
my $everyone = Foo::Bar::User->count;
my $young = Foo::Bar::User->count( 'where age <= ?', 13 );
You can count the total number of elements in a table by calling the count method with no arguments. And you can narrow that count by passing SQL conditions to the method in the same manner as with the select method. math but make it cute π’
delete
# Delete a single object from the database
$user->delete;
# Delete a range of rows from the database
Foo::Bar::User->delete( 'where age <= ?', 13 );
The delete method deletes the single row representing an object, based on the primary key or SQLite rowid of that object.
And here's the cute part: the object you delete is left intact and untouched, and you remain free to do with it whatever you wish. it's still yours, bestie π
delete_where
# Delete a range of rows from the database
Foo::Bar::User->delete( 'age <= ?', 13 );
The delete_where static method lets you delete large numbers of rows from a database while protecting you from accidentally doing a boundless delete β the truncate method is provided specifically for that purpose. π‘οΈ
It takes the same parameters for deleting as the select method, with the exception that the where keyword is automatically provided for you and should not be passed in.
This ensures that providing an empty or null condition results in an invalid SQL query and the deletion will not occur. safety first, always β οΈ
Returns the number of rows deleted from the database (which may be zero).
truncate
# Clear out all records from the table
Foo::Bar::User->truncate;
The truncate method takes no parameters and is used for exactly one purpose: to completely empty a table of all rows. π
Having a separate method from delete not only prevents accidents, but will also do the deletion via the direct SQLite TRUNCATE TABLE query. This uses a different deletion mechanism, and is significantly faster than a plain SQL DELETE. speed AND safety?? we love to see it π₯
π TO DO
- Support for intuiting reverse relations from foreign keys
- Document the 'create' and 'table' params
π SUPPORT
Bugs should be reported via the CPAN bug tracker at
<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=ORLite>
For other issues, contact the author.
π AUTHOR
Adam Kennedy <adamk@cpan.org>
π SEE ALSO
ORLite::Mirror, ORLite::Migrate, ORLite::Pod
π COPYRIGHT
Copyright 2008 - 2012 Adam Kennedy.
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
The full text of the license can be found in the LICENSE file included with this module.
and that's the whole page bestie!! π this version is from perl v5.36.0, dated 2023-10-27, and it's the ORLite(3pm) manpage. go build something iconic with it πβ¨π¦
π¦ where this page comes from
- debian trixie: liborlite-perl 2.00-1 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu noble: liborlite-perl 2.00-1 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu resolute: liborlite-perl 2.00-1 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu stonking: liborlite-perl 2.00-1 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms