Class: SuperAuth::Authorization

Inherits:
Object
  • Object
show all
Defined in:
lib/super_auth/authorization.rb

Class Method Summary collapse

Class Method Details

.assert_compilable!Object

What must hold before the compiled table is touched, in the order the failures are worst. A parent_id cycle in any tree table first: the walks terminate on one, so it does not fail a compile — it quietly makes every node in the cycle an ancestor of every other, and a grant on any of them reaches all their subtrees. Then the resource tree's own rule, that type-level nodes are flat.



42
43
44
45
46
47
# File 'lib/super_auth/authorization.rb', line 42

def assert_compilable!
  SuperAuth::Group.assert_acyclic!
  SuperAuth::Role.assert_acyclic!
  SuperAuth::Resource.assert_acyclic!
  SuperAuth::Resource.assert_compilable!
end

.compile!Object

Clears and repopulates the compiled table from the current graph, inside one transaction, and returns the row count. One INSERT ... SELECT of SuperAuth::Edge.authorizations, like the ActiveRecord twin: row by row the same graph loaded at ~570 rows/s through the model, which at a million rows is most of an hour in one held transaction, with every row resident in Ruby. Runtime enforcement (ByCurrentUser, the RLS policies) reads only this table, so every edit to the graph is inert until this runs. The guards run first, before the delete, so a refused compile leaves the previous rows in place rather than an empty table.

Postgres JIT-compiles the union's expressions on every run: 539 LLVM functions, 1.6-2.2s of optimisation and emission for a query that then executes in milliseconds. SET LOCAL scopes the switch to this transaction, so nothing leaks to the pooled connection.

Everything here runs on SuperAuth.db, named, not on this class's own db. A host loads the models at require time, before it has connected anything, so Sequel binds them to whatever Sequel::Model.db is then — a mock in a Rails boot — and SuperAuth.db= rebinds them afterwards; the first consumer's binding of this one class had been left behind for months and nothing noticed until a branch on db.database_type here skipped the timestamp cast and Postgres refused the INSERT.



25
26
27
28
29
30
31
32
33
34
# File 'lib/super_auth/authorization.rb', line 25

def compile!
  SuperAuth.db.transaction do
    SuperAuth.db.run "SET LOCAL jit = off" if SuperAuth.db.database_type == :postgres
    assert_compilable!
    table = SuperAuth.db[:super_auth_authorizations]
    table.delete
    table.insert(SuperAuth::Edge::AUTHORIZATION_COLUMNS, compile_source)
    table.count
  end
end

.compile_sourceObject

The SELECT the compile inserts from. The eight timestamp columns travel through the union as text (the MySQL collation reason in SuperAuth::Edge.string_cast_type), and Postgres has no assignment cast from text to timestamp: inserting the bare union there fails with "column ... is of type timestamp ... but expression is of type text". They are cast back on Postgres only. MySQL converts on assignment, and SQLite's CAST(... AS timestamp) has NUMERIC affinity, which would keep the "2026" of a date and drop the rest.



57
58
59
60
61
62
63
64
65
# File 'lib/super_auth/authorization.rb', line 57

def compile_source
  graph = SuperAuth::Edge.authorizations
  return graph unless SuperAuth.db.database_type == :postgres

  columns = SuperAuth::Edge::AUTHORIZATION_COLUMNS.map do |column|
    column.end_with?("_at") ? Sequel.cast(column, :timestamp).as(column) : column
  end
  graph.from_self(alias: :graph).select(*columns)
end