当前位置: 首页 > 软件库 > 数据库相关 > >

clear

Advanced ORM between postgreSQL and Crystal
授权协议 MIT License
开发语言 C/C++
所属分类 数据库相关
软件类型 开源软件
地区 不详
投 递 者 从景曜
操作系统 跨平台
开源组织
适用人群 未知
 软件概览

Clear

Clear is an ORM built specifically for PostgreSQL in Crystal.

It's probably the most advanced ORM for PG on Crystal in term of features offered.It features Active Record pattern models, and a low-level SQL builder.

You can deal out of the box with jsonb, tsvectors, cursors, CTE, bcrypt password,array, uuid primary key, foreign constraints... and other things !It also has a powerful DSL to construct where and having clauses.

The philosophy beneath is to please me (and you !) with emphasis made on businesscode readability and minimal setup.

The project is quite active and well maintened, too !

Resources

Why to use Clear ?

In few seconds, you want to use Clear if:

  • You want an expressive ORM. Put straight your thought to your code !
  • You'd like to use advanced Postgres features without hassle
  • You are aware of the pros and cons of the Active Records pattern

You don't want to use Clear if:

  • You're not willing to use PostgreSQL
  • You're looking for a minimalist ORM / Data Mapper
  • You need something which doesn't evolve, with breaking changes.Clear is still in alpha but starting to mature !

Features

  • Active Record pattern based ORM
  • Expressiveness as mantra - even with advanced features like jsonb, regexp... -
# Like ...
  Product.query.where{ ( type == "Book" ) & ( metadata.jsonb("author.full_name") == "Philip K. Dick" ) }
  # ^--- will use @> operator, to relay on your gin index. For real.

  Product.query.where{ ( products.type == "Book" ) & ( products.metadata.jsonb("author.full_name") != "Philip K. Dick" ) }
  # ^--- this time will use -> notation, because no optimizations possible :/

  # Or...
  User.query.where{ created_at.in? 5.days.ago .. 1.day.ago }

  # Or even...
  ORM.query.where{ ( description =~ /(^| )awesome($| )/i ) }.first!.name # Clear! :-)
  • Proper debug information
    • Log and colorize query. Show you the last query if your code crash !
    • If failing on compile for a good reason, give proper explaination (or at least try)
  • Migration system
  • Validation system
  • N+1 query avoidance strategy
  • Transaction, rollback & savepoint
  • Access to CTE, locks, cursors, scope, pagination, join, window, multi-connection and many others features
  • Model lifecycle/hooks
  • JSONB, UUID, FullTextSearch

Installation

In shards.yml

dependencies:
  clear:
    github: anykeyh/clear
    branch: master

Then:

require "clear"

Model definition

Clear offers some mixins, just include them in your classes to clear them:

Column mapping

class User
  include Clear::Model

  column id : Int64, primary: true

  column email : String

  column first_name : String?
  column last_name : String?

  column encrypted_password : Crypto::Bcrypt::Password

  def password=(x)
    self.encrypted_password = Crypto::Bcrypt::Password.create(password)
  end
end

Column types

  • Number, String, Time, Boolean and Jsonb structures are already mapped.
  • Numeric (arbitrary precision number) is also supported
  • Array of primitives too.For other type of data, just create your own converter !
class Clear::Model::Converter::MyClassConversion
  def self.to_column(x) : MyClass?
    case x
    when String
      MyClass.from_string(x)
    when Slice(UInt8)
      MyClass.from_slice(x)
    else
      raise "Cannot convert from #{x.class} to MyClass"
    end
  end

  def self.to_db(x : UUID?)
    x.to_s
  end
end

Clear::Model::Converter.add_converter("MyClass", Clear::Model::Converter::MyClassConversion)
Column presence

Most of the ORM for Crystal are mapping column type as Type | Nil union.It makes sens so we allow selection of some columns only of a model.However, this have a caveats: columns are still accessible, and will return nil,even if the real value of the column is not null !

Moreover, most of the developers will enforce nullity only on their programminglanguage level via validation, but not on the database, leading to inconsistency.

Therefore, we choose to throw exception whenever a column is accessed beforeit has been initialized and to enforce presence through the union system ofCrystal.

Clear offers this through the use of column wrapper.Wrapper can be of the type of the column as in postgres, or in UNKNOWN state.This approach offers more flexibility:

User.query.select("last_name").each do |usr|
  puts usr.first_name #Will raise an exception, as first_name hasn't been fetched.
end

u = User.new
u.first_name_column.defined? #Return false
u.first_name_column.value("") # Call the value or empty string if not defined :-)
u.first_name = "bonjour"
u.first_name_column.defined? #Return true now !

Wrapper give also some pretty useful features:

u = User.new
u.email = "me@myaddress.com"
u.email_column.changed? # TRUE
u.email_column.revert
u.email_column.defined? # No more

Associations

Clear offers has_many, has_one, belongs_to and has_many through associations:

class Security::Action
  belongs_to role : Role
end

class Security::Role
  has_many user : User
end

class User
  include Clear::Model

  has_one user_info : UserInfo
  has_many posts : Post

  belongs_to role : Security::Role

  # Use of the standard keys (users_id <=> security_role_id)
  has_many actions : Security::Action, through: Security::Role
end

Querying

Clear offers a collection system for your models. The collection systemtakes origin to the lower API Clear::SQL, used to build requests.

Simple query

Fetch a model

To fetch one model:

# 1. Get the first user
User.query.first #Get the first user, ordered by primary key

# Get a specific user
User.find!(1) #Get the first user, or throw exception if not found.

# Usage of query provides a `find_by` kind of method:
u : User? = User.query.find{ email =~ /yacine/i }
Fetch multiple models

To prepare a collection, juste use Model#query.Collections include SQL::Select object, so all the low level API(where, join, group_by, lock...) can be used in this context.

# Get multiple users
User.query.where{ (id >= 100) & (id <= 200) }.each do |user|
  # Do something with user !
end

#In case you know there's millions of row, use a cursor to avoid memory issues !
User.query.where{ (id >= 1) & (id <= 20_000_000) }.each_cursor(batch: 100) do |user|
  # Do something with user; only 100 users will be stored in memory
  # This method is using pg cursor, so it's 100% transaction-safe
end
Aggregate functions

Call aggregate functions from the query is possible. For complex aggregation,I would recommend to use the SQL::View API (note: Not yet developed),and keep the model query for fetching models only

# count
user_on_gmail = User.query.where{ email.ilike "@gmail.com%" }.count #Note: count return is Int64
# min/max
max_id = User.query.where{ email.ilike "@gmail.com%" }.max("id", Int32)
# your own aggregate
weighted_avg = User.query.agg( "SUM(performance_weight * performance_score) / SUM(performance_weight)", Float64 )
Fetching associations

Associations are basically getter which create predefined SQL.To access to an association, just call it !

User.query.each do |user|
  puts "User #{user.id} posts:"
  user.posts.each do |post| #Works, but will trigger a request for each user.
    puts "#{post.id}"
  end
end
Caching association for N+1 request

For every association, you can tell Clear to encache the results to avoidN+1 queries, using with_XXX on the collection:

# Will call two requests only.
User.query.with_posts.each do |user|
  puts "User #{user.id} posts:"
  user.posts.each do |post|
    puts "#{post.id}"
  end
end

Note than Clear doesn't perform a join method, and the SQL produced will usethe operator IN on the association.

In the case above:

  • The first request will be
  SELECT * FROM users;
  • Thanks to the cache, a second request will be called before fetching the users:
  SELECT * FROM posts WHERE user_id IN ( SELECT id FROM users )

I have plan in a late future to offer different query strategies for the cache (e.g. joins, unions...)

Associations caching examples

When you use the caching system of the association, using filters on association willinvalidate the cache, and N+1 query will happens.

For example:

User.query.with_posts.each do |user|
  puts "User #{user.id} published posts:"
  # Here: The cache system will not work. The cache on association
  # is invalidated by the filter `where`.
  user.posts.where({published: true}).each do |post|
    puts "#{post.id}"
  end
end

The way to fix it is to filter on the association itself:

User.query.with_posts(&.where({published: true})).each do |user|
  puts "User #{user.id} published posts:"
  # The posts collection of user is already encached with the published filter
  user.posts.each do |post|
    puts "#{post.id}"
  end
end

Note than, of course in this example user.posts are not ALL the posts but only thepublished posts

Thanks to this system, we can stack it to encache long distance relations:

# Will cache users<=>posts & posts<=>category
# Total: 3 requests !
User.query.with_posts(&.with_category).each do |user|
  #...
end
Querying computed or foreign columns

In case you want columns computed by postgres, or stored in another table, you can use fetch_column.By default, for performance reasons, fetch_columns option is set to false.

users = User.query.select(email: "users.email",
  remark: "infos.remark").join("infos"){ infos.user_id == users.id }.to_a(fetch_columns: true)

# Now the column "remark" will be fetched into each user object.
# Access can be made using `[]` operator on the model.

users.each do |u|
  puts "email: `#{u.email}`, remark: `#{u["remark"]?}`"
end

Inspection & SQL logging

Inspection

inspect over model offers debugging insights:

  p # => #<Post:0x10c5f6720
          @attributes={},
          @cache=
           #<Clear::Model::QueryCache:0x10c6e8100
            @cache={},
            @cache_activation=Set{}>,
          @content_column=
           "...",
          @errors=[],
          @id_column=38,
          @persisted=true,
          @published_column=true,
          @read_only=false,
          @title_column="Lorem ipsum torquent inceptos"*,
          @user_id_column=5>

In this case, the * means a column is changed and the object is dirty and diverge from the database.

SQL Logging

One thing very important for a good ORM is to offer vision of the SQLcalled under the hood.Clear is offering SQL logging tools, with SQL syntax colorizing in your terminal.

For activation, simply setup the log to :debug level

::Log.builder.bind "clear.*", Log::Severity::Debug, Log::IOBackend.new

Save & validation

Save

Object can be persisted, saved, updated:

u = User.new
u.email = "test@example.com"
u.save! #Save or throw if unsavable (validation failed).

Columns can be checked & reverted:

u = User.new
u.email = "test@example.com"
u.email_column.changed? # < Return "true"
u.email_column.revert # Return to #undef.

Validation

Presence validator

Presence validator is done using the type of the column:

class User
  include Clear::Model

  column first_name : String # Must be present
  column last_name : String? # Can be null
end
NOT NULL DEFAULT ... CASE

There's a case when a column CAN be null inside Crystal, if not persisted,but CANNOT be null inside Postgres.

It's for example the case of the id column, which take value after saving !

In this case, you can write:

class User
    column id : Int64, primary: true, presence: false #id will be set using pg serial !
end

Thus, in all case this will fail:

u = User.new
u.id # raise error
Other validators

When you save your model, Clear will call first the presence validators, thencall your custom made validators. All you have to do is to reimplementthe validate method:

class MyModel
#...
  def validate
    # Your code goes here
  end
end

Validation fails if model#errors is not empty:

class MyModel
    #...
    def validate
      if first_name_column.defined? && first_name != "ABCD" #< See below why `defined?` must be called.
        add_error("first_name", "must be ABCD!")
      end
    end
  end
Unique validator

Please use unique feature of postgres. Unique validator at crystal level is anon-go and lead to terrible race concurrency issues if your deploy on multiple nodes/pods.It's an anti-pattern and must be avoided at any cost.

The validation and the presence system

In the case you try validation on a column which has not been initialized,Clear will complain, telling you you cannot access to the column.Let's see an example here:

class MyModel
  #...
  def validate
    add_error("first_name", "should not be empty") if first_name == ""
  end
end

MyModel.new.save! #< Raise unexpected exception, not validation failure :(

This validator will raise an exception, because first_name has never been initialized.To avoid this, we have many way:

# 1. Check presence:

def validate
  if first_name_column.defined? #Ensure we have a value here.
    add_error("first_name", "should not be empty") if first_name == ""
  end
end

# 2. Use column object + default value
def validate
  add_error("first_name", "should not be empty") if first_name_column.value("") == ""
end

# 3. Use the helper macro `on_presence`
def validate
  on_presence(first_name) do
    add_error("first_name", "should not be empty") if first_name == ""
  end
end

#4. Use the helper macro `ensure_than`
def validate
  ensure_than(first_name, "should not be empty", &.!=(""))
end

#5. Use the `ensure_than` helper (but with block notation) !
def validate
  ensure_than(first_name, "should not be empty") do |column|
    column != ""
  end
end

I recommend the 4th method in most of the cases you will faces.Simple to write and easy to read !

Migration

Clear offers of course a migration system.

Migration should have an order column set.This number can be wrote at the end of the class itself:

class Migration1
  include Clear::Migration

  def change(dir)
    #...
  end
end

Using filename

Another way is to write down all your migrations one file per migration, andnaming the file using the [number]_migration_description.cr pattern.In this case, the migration class name doesn't need to have a number at the end of the class name.

# in src/db/migrations/1234_create_table.cr
class CreateTable
  include Clear::Migration

  def change(dir)
    #...
  end
end

Migration examples

Migration must implement the method change(dir : Migration::Direction)

Direction is the current direction of the migration (up or down).It provides few methods: up?, down?, up(&block), down(&block)

You can create a table:

def change(dir)
    create_table(:test) do |t|
      t.column :first_name, :string, index: true
      t.column :last_name, :string, unique: true

      t.index "lower(first_name || ' ' || last_name)", using: :btree

      t.timestamps
    end
  end

Constraints

I strongly encourage to use the foreign key constraints of postgres for your references:

t.references to: "users", on_delete: "cascade", null: false

There's no plan to offer on Crystal level the on_delete feature, likedependent in ActiveRecord. That's a standard PG feature, just set itup in migration

Performances

Models add a layer of computation. Below is a sample with a very simple model(two integer column ), with fetching of 100k rows over 1M rows database, using --release flag:

Method Total time Speed
Simple load 100k 12.04 ( 83.03ms) (± 3.87%) 2.28× slower
With cursor 8.26 ( 121.0ms) (± 1.25%) 3.32× slower
With attributes 10.30 ( 97.12ms) (± 4.07%) 2.67× slower
With attributes and cursor 7.55 (132.52ms) (± 2.39%) 3.64× slower
SQL only 27.46 ( 36.42ms) (± 5.05%) fastest
  • Simple load 100k is using an array to fetch the 100k rows.
  • With cursor is querying 1000 rows at a time
  • With attribute setup a hash to deal with unknown attributes in the model (e.g. aggregates)
  • With attribute and cursor is doing cursored fetch with hash attributes created
  • SQL only build and execute SQL using SQL::Builder

As you can see, it takes around 100ms to fetch 100k rows for this simple model (SQL included).If for more complex model, it would take a bit more of time, I think the performancesare quite reasonable, and tenfold or plus faster than Rails's ActiveRecord.

Licensing

This shard is provided under the MIT license.

Contribution

All contributions are welcome ! As a specialized ORM for PostgreSQL,be sure a great contribution on a very specific PG feature will be incorporatedto this shard.I hope one day we will cover all the features of PG here !

Running Tests

In order to run the test suite, you will need to have the PostgresSQL service locally available via a socket for access with psql. psql will attempt to use the 'postgres' user to create the test database. If you are working with a newly installed database that may not have the postgres user, this can be created with createuser -s postgres.

Contributors

Thanks goes to these wonderful people (emoji key):


Russ Smith

��

Elias Perez

��

Jeremy Woertink

��

Anton Maminov

��

remydev

��

Jack Turnbull

��

Blacksmoke16

��

luigi

��

Matthias Zauner

��

Weston Ganger

��

Pynix Wang

��

Vici37

��

Niklas Karoly

��

Massimiliano Bertinetti

��

batarian71

��

Yacine Petitprez

��

Alexandre

��

Anh (Duke) Nguyen

��

Ryan Westlund

��

Caspian Baska

��

This project follows the all-contributors specification. Contributions of any kind welcome!

  • /************************************************/ /* 函数功能:初始化UART口 */ /************************************************/ void UART_init(uint32_t baudrate) { uint32_t DL_value,Clear=Clear; // (用这种方式定义

  • 本文整理汇总了Python中IPython.display.clear_output方法的典型用法代码示例。如果您正苦于以下问题:Python display.clear_output方法的具体用法?Python display.clear_output怎么用?Python display.clear_output使用的例子?那么恭喜您, 这里精选的方法代码示例或许可以为您提供帮助。您也可以进一步

  • 显示行号 | 选择喜欢的代码风格 默认 GitHub Dune LakeSide Plateau Vibrant Blue Eighties Tranquil clear 命令清除当前屏幕终端上的任何信息。 clear 命令安装: -bash: clear command not found #Debian apt-get install libncurses5-dbg #Ubuntu apt-g

  • clear的意思 adj. 清楚的,明白的,清晰的,明亮的,清澈的,明确的 adv. 完全地,清晰地,整整 vi. 变明朗,变清澈 vt. 扫除,除去,消除(嫌疑),使清楚,使干净 n. 空隙,空间 变形:比较级:clearer; 最高级:clearest; 过去式: cleared; 现在分词:clearing; 过去分词:cleared; clear用法 clear可以用作形容词 clear的

  • 自己太蠢代码如下: List<Map<String, Object>> listMap = new ArrayList<Map<String, Object>>(); Map<String, Object> map = new HashMap<String, Object>(); map.put(“key”, 1); listMap.add(map); map.clear(); map.put(“

  • 01、文章目录 02、命令介绍 clear命令用于清除当前屏幕终端上的所有信息。 03、命令格式 用法:clear 04、常用选项 无 05、参考示例 5.1 终端清屏 [deng@itcast test]$ umask 0022 [deng@itcast test]$ umask 0022 [deng@itcast test]$ umask 0022 [deng@itcast test]$

  • 用来清除数组中,或者列表中的数据的 为了避免数据的叠加。就需要在加载前 用 数组.clear();清除数据 public Tree(Object data) { this.data = data; childs = new ArrayList(); childs.clear(); }

  • clear与float的含义:float:left; 当前元素向左侧浮动.float:right: 当前元素向右侧浮动. clear:left; 禁止左侧出现浮动元素,如果左侧存在浮动元素,则当前元素将在浮动元素下面另起一行呈现.clear:right; 禁止右侧出现浮动元素,如果右侧存在浮动元素,则右侧的浮动元素将在当前元素下面另起一行呈现.clear:both; 禁止左右两侧出现浮动元素,当前

  • 怎么用clear case? 罗索工作室 (http://www.roarsoft.net) 添加者:iwgh 加入时间:2005-12-5 16:11:00 1ClearCase简介   ClearCase是一种配置管理工具,由Rational公司开发,是开发小组用来跟踪、管理软件开发过程各个工件的配置管理系统,ClearCase可以协助开发组织更好地管理软件开发进程。 ClearCase可以和

  • 首先更正一点clear不是一个标签,它是css中的一个属性。 其属性值有四个clear:both|left|right|none。 简单来说,clear的作用是“清除”浮动。 如果某元素设置clear:left,表示该元素左边不存在浮动元素,相应的,clear:right表示该元素右边不存在浮动元素;clear:both表示两边都不存在浮动元素。clear:none表示两边允许有浮动元素。 在视觉

  •   具体的说是做配置管理的工具,只是SCM管理工具其中的一种。是RATIONAL公司开发的配置管理工具,类似于VSS,CVS的作用,但是功能比VSS,CVS强大的多,而且可以与WINDOWS资源管理器集成使用,并且还可以与很多开发工具集成在一起使用。但是对配置管理员的要求比较高。而且RATIONAL的产品一般都很贵。如果自己找到CRACK的话就没有技术支持了。   随着软件团队人员的增加,软件版本

  • Clearwater IMS 是一个开源的IMS项目,提供VoIP/SIP电话等功能,Clearwater IMS主页https://clearwater.readthedocs.io/en/stable/

  • 准确理解CSS clear:left/right的含义及实际用途

  • vector的clear操作可以将vector的数据清空,但是vector的容量不会变化,即分配给vector的内存不会收回。 vector<int> num={1,2,3,4,5}; num.clear(); resize()的作用是改变vector中元素的数目。 vector<int> num={1,2,3,4,5}; num.resize(0); 如果n比当前的vector元素数目要小,ve

  • http://www.investopedia.com/terms/c/clearingcorporation.asp#axzz2FObjSxdk Definition of 'Clearing Corporation' An organization associated with an exchange to handle the confirmation, settlement and de

  • map是以hash为原理实现的。 所以关于map的clear就是删除对应关系,但保留键值key hash表的每个位置都会预留空间(即使没有元素),插入/删除直接就是把元素放在相应位置/从相应位置移除。 ———来自百度

  • 今天编写C#遇到这个问题,代码如下,不知朋友们有没有碰到过 private void button1_Click_1(object sender, EventArgs e) { this.listView1.Items.Clear(); ListViewItem li = new ListViewItem("w");

  • 在当今web2.0时代,在设计网页的时候大家都会使用div+css进行布局,有点就不用多说了,自然有很多好处了,至少会比用table布局少些代码,而用div布局也有他们的不方便之处,就是比较复杂,尤其是多个div在一行显示,多个浏览器兼容等等,所以初学者还是尽量用table布局,待熟练了在用div. 现在说的是DIV+CSS中clear:both的作用,先看下例: p.f1{float:left;

  • clear 指令用来清除终端屏幕,在终端中通过快捷键 Ctrl+L 清除屏幕 read 命令从标准输入中读取一行,并把输入行的每个字段的值指定给 shell 变量 wc命令用于计算字数。 利用wc指令我们可以计算文件的Byte数、字数、或是列数,若不指定文件名称、或是所给予的文件名为"-",则wc指令会从标准输入设备读取数据。 语法 wc [-clw][–help][–version][文件…]

相关阅读

相关文章

相关问答

相关文档