| Commit message (Collapse) | Author | Age | Files | Lines |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
#36293 was an issue for through association with `joins` for a long
time, but since #35864 through association with `left_joins` would also
be affected by the issue.
Implicit through table joins should be appeared before user supplied
joins, otherwise loading through association with joins will cause a
statement invalid error.
Fixes #36293.
```
% ARCONN=postgresql bundle exec ruby -w -Itest test/cases/associations/has_many_through_associations_test
.rb -n test_through_association_with_joins
Using postgresql
Run options: -n test_through_association_with_joins --seed 7116
# Running:
E
Error:
HasManyThroughAssociationsTest#test_through_association_with_joins:
ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR: missing FROM-clause entry for table "posts"
LINE 1: ... "comments_posts" ON "comments_posts"."post_id" = "posts"."i...
^
: SELECT "comments".* FROM "comments" INNER JOIN "comments" "comments_posts" ON "comments_posts"."post_id" = "posts"."id" INNER JOIN "posts" ON "comments"."post_id" = "posts"."id" WHERE "posts"."author_id" = $1
rails test test/cases/associations/has_many_through_associations_test.rb:61
Finished in 0.388657s, 2.5730 runs/s, 0.0000 assertions/s.
1 runs, 0 assertions, 0 failures, 1 errors, 0 skips
```
|
|\
| |
| | |
Use a single thread for all ConnectionPool Reapers
|
| |
| |
| |
| |
| | |
Previously we would spawn one thread per connection pool, which ends up
being wasteful for apps with several connection pools.
|
|\ \
| | |
| | | |
Fix eager loading associations with string joins not to raise NoMethodError
|
| | |
| | |
| | |
| | | |
Fixes #34456.
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | | |
This partly reverts the effect of d1107f4d.
d1107f4d makes `touch` tracks the mutation whether the `touch` is
occurred by explicit or not.
Existing apps expects that the previous changes tracks only the changes
which is explicit action by users.
I'd revert the implicit `touch` mutation tracking since I'd not like to
break existing apps.
Fixes #36219.
|
| | | |
|
|\ \ \
| | | |
| | | | |
Remove SQLite version support caveats [ci skip]
|
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | | |
Since d1a74c1e012ed96f7179e53b9190b7da0a369e11, Active Record requires
SQLite version 3.8.0 or greater, so savepoints and partial indexes are
always available.
That commit also added a runtime version check, so we can remove the
minimum version requirement from the internal adapter documentation.
|
|\ \ \ \
| |/ / /
|/| | | |
document update_counters on relation [ci skip]
|
| | | | |
|
|\ \ \ \
| |_|_|/
|/| | |
| | | | |
Closes #28707.
|
| | | |
| | | |
| | | |
| | | | |
Fixes GH#28706. Now rails g migration create_users and rails g model User have the same behavior for timestamps since they implement the same migration template. The expected behavior is that this create table migration will create the table with timestamps unless you pass --no-timestamps or --skip-timestamps to the generator. The expected migration should match what you get when you use the model generator. Using the migration generator, which doesn't have a class_option for timestamps would cause them to not be added to the migration file. Now the migration behavior of the migration generator, create_table only, is aligned with the migration behavior of the model generator. Also modified relevant example of ActiveRecord Migrations Guide.
|
| |_|/
|/| |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | | |
This commit adds "TRANSACTION" to savepoint and commit, rollback statements
because none of savepoint statements were removed by #36153 since they are not "SCHEMA" statements.
Although, only savepoint statements can be labeled as "TRANSACTION"
I think all of transaction related method should add this label.
Follow up #36153
|
| | |
| | |
| | |
| | |
| | | |
The initializer receives `nil` for these options when no cofigurations were given:
https://github.com/rails/rails/blob/v6.0.0.rc1/activerecord/lib/active_record/railtie.rb#L91-L97
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | | |
transaction
Currently, `committed!`/`rolledback!` will only be attempted for the
first enrolled record in the transaction, that will cause some
problematic behaviors.
The first one problem, `clear_transaction_record_state` won't be called
even if the transaction is finalized except the first enrolled record.
This means that de-duplicated records in the transaction won't refer
latest state (e.g. won't happen rolling back record state).
The second one problem, the enrolled order is not always the same as the
order in which the actions actually happened, the first enrolled record
may succeed no actions (e.g. `destroy` has already succeeded on another
record during `before_destroy`), it will lose to fire any transactional
callbacks.
To avoid both problems, we should attempt `committed!`/`rolledback!` to
all enrolled records in the transaction.
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | | |
with `ActiveRecord::BindParameterTest#test_too_many_binds`
sqlite adapter has its own `bind_params_length`, `ActiveRecord::BindParameterTest#test_too_many_binds` respects it.
* Modified `ActiveRecord::BindParameterTest#test_too_many_binds` to show `bind_params_length` value
```
$ git diff
diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb
index 85685d1d00..83cd07f1d7 100644
--- a/activerecord/test/cases/bind_parameter_test.rb
+++ b/activerecord/test/cases/bind_parameter_test.rb
@@ -108,6 +108,7 @@ def test_statement_cache_with_sql_string_literal
def test_too_many_binds
bind_params_length = @connection.send(:bind_params_length)
+ p bind_params_length
topics = Topic.where(id: (1 .. bind_params_length).to_a << 2**63)
assert_equal Topic.count, topics.count
$
```
* Executed modified `ActiveRecord::BindParameterTest#test_too_many_binds`
```
$ bin/test test/cases/bind_parameter_test.rb -n test_too_many_binds
Using sqlite3
Run options: -n test_too_many_binds --seed 47321
999
.
Finished in 0.075249s, 13.2892 runs/s, 26.5784 assertions/s.
1 runs, 2 assertions, 0 failures, 0 errors, 0 skips
$
```
|
| | | |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | | |
* Make scope arity check consistent
* Add test for arity change
[Rob Trame + Rafael Mendonça França]
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | | |
Every database executes different type of sql statement to get metadata then `ActiveRecord::TestCase` ignores these database specific sql statements to make `assert_queries` or `assert_no_queries` work consistently.
Connection adapter already labels these statement by setting "SCHEMA" argument, this pull request makes use of "SCHEMA" argument to ignore metadata queries.
Here are the details of these changes:
* PostgresqlConnectionTest
Each of PostgresqlConnectionTest modified just executes corresponding methods
https://github.com/rails/rails/blob/fef174f5c524edacbcad846d68400e7fe114a15a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb#L182-L195
```ruby
# Returns the current database encoding format.
def encoding
query_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = current_database()", "SCHEMA")
end
# Returns the current database collation.
def collation
query_value("SELECT datcollate FROM pg_database WHERE datname = current_database()", "SCHEMA")
end
# Returns the current database ctype.
def ctype
query_value("SELECT datctype FROM pg_database WHERE datname = current_database()", "SCHEMA")
end
```
* BulkAlterTableMigrationsTest
mysql2 adapter executes `SHOW KEYS FROM ...` to see if there is an index already created as below. I think the main concerns of these tests are how each database adapter creates or drops indexes then ignoring `SHOW KEYS FROM` statement makes sense.
https://github.com/rails/rails/blob/fef174f5c524edacbcad846d68400e7fe114a15a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb#L11
```ruby
execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result|
```
* Temporary change not included in this commit to show which statements executed
```diff
$ git diff
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 8e8ed494d9..df05f9bd16 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -854,7 +854,7 @@ def test_adding_indexes
classname = ActiveRecord::Base.connection.class.name[/[^:]*$/]
expected_query_count = {
- "Mysql2Adapter" => 3, # Adding an index fires a query every time to check if an index already exists or not
+ "Mysql2Adapter" => 1, # Adding an index fires a query every time to check if an index already exists or not
"PostgreSQLAdapter" => 2,
}.fetch(classname) {
raise "need an expected query count for #{classname}"
@@ -886,7 +886,7 @@ def test_removing_index
classname = ActiveRecord::Base.connection.class.name[/[^:]*$/]
expected_query_count = {
- "Mysql2Adapter" => 3, # Adding an index fires a query every time to check if an index already exists or not
+ "Mysql2Adapter" => 1, # Adding an index fires a query every time to check if an index already exists or not
"PostgreSQLAdapter" => 2,
}.fetch(classname) {
raise "need an expected query count for #{classname}"
$
```
* Executed these modified tests
```ruby
$ ARCONN=mysql2 bin/test test/cases/migration_test.rb -n /index/
Using mysql2
Run options: -n /index/ --seed 8462
F
Failure:
BulkAlterTableMigrationsTest#test_adding_indexes [/home/yahonda/git/rails/activerecord/test/cases/migration_test.rb:863]:
3 instead of 1 queries were executed.
Queries:
SHOW KEYS FROM `delete_me`
SHOW KEYS FROM `delete_me`
ALTER TABLE `delete_me` ADD UNIQUE INDEX `awesome_username_index` (`username`), ADD INDEX `index_delete_me_on_name_and_age` (`name`, `age`).
Expected: 1
Actual: 3
bin/test test/cases/migration_test.rb:848
F
Failure:
BulkAlterTableMigrationsTest#test_removing_index [/home/yahonda/git/rails/activerecord/test/cases/migration_test.rb:895]:
3 instead of 1 queries were executed.
Queries:
SHOW KEYS FROM `delete_me`
SHOW KEYS FROM `delete_me`
ALTER TABLE `delete_me` DROP INDEX `index_delete_me_on_name`, ADD UNIQUE INDEX `new_name_index` (`name`).
Expected: 1
Actual: 3
bin/test test/cases/migration_test.rb:879
..
Finished in 0.379245s, 10.5473 runs/s, 7.9105 assertions/s.
4 runs, 3 assertions, 2 failures, 0 errors, 0 skips
$
```
* ActiveRecord::ConnectionAdapters::Savepoints
Left `self.ignored_sql` to ignore savepoint related statements because these SQL statements are not related "SCHEMA"
```
self.ignored_sql = [/^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/]
```
https://github.com/rails/rails/blob/fef174f5c524edacbcad846d68400e7fe114a15a/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb#L10-L20
```ruby
def create_savepoint(name = current_savepoint_name)
execute("SAVEPOINT #{name}")
end
def exec_rollback_to_savepoint(name = current_savepoint_name)
execute("ROLLBACK TO SAVEPOINT #{name}")
end
def release_savepoint(name = current_savepoint_name)
execute("RELEASE SAVEPOINT #{name}")
end
```
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | | |
If the same id's records are saved and/or destroyed in the transaction,
commit callbackes will only run for the first enrolled record.
https://github.com/rails/rails/blob/a023e2180093ebc517a642aaf21f3c7241c67657/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb#L115-L119
The regression #36132 is caused due to #35920 changed the enrollment
order that the first action's record will be enrolled to last in the
transaction.
We could not change the the enrollment order as long as someone depends
on the enrollment order.
Fixes #36132.
|
| | | |
|
| | | |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | | |
This fixes a regression for #35864.
Usually, stashed joins (mainly eager loading) are performed as LEFT
JOINs.
But the case of merging joins/left_joins of different class, that
(stashed) joins are performed as the same `join_type` as the parent
context for now.
Since #35864, both (joins/left_joins) stashed joins might be contained
in `joins_values`, so each stashed joins should maintain its own
`join_type` context.
Fixes #36103.
|
|\ \ \
| | | |
| | | | |
Model error as object
|
| | | | |
|
| | | |
| | | |
| | | |
| | | | |
Revert some tests to ensure back compatibility
|
| | | |
| | | |
| | | |
| | | |
| | | | |
autosave duplicate errors can be removed
See SHA 7550f0a016ee6647aaa76c0c0ae30bebc3867288
|
| | | | |
|
| | | | |
|
| | | | |
|
| | | | |
|
|\ \ \ \
| | | | |
| | | | |
| | | | |
| | | | |
| | | | | |
abhaynikam/35866-add-touch-option-for-has-one-association
Adds missing touch option to has_one association
|
| | | | | |
|
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | | |
If we want to get alias resolved attribute finally, we can use
`attribute_alias` directly.
For that purpose, avoiding redundant `attribute_alias?` makes alias
attribute access 40% faster.
https://gist.github.com/kamipo/e427f080a27b46f50bc508fae3612a0e
Before (2c0729d8):
```
Warming up --------------------------------------
user['id'] 102.668k i/100ms
user['new_id'] 80.660k i/100ms
user['name'] 99.368k i/100ms
user['new_name'] 81.626k i/100ms
Calculating -------------------------------------
user['id'] 1.431M (± 4.0%) i/s - 7.187M in 5.031985s
user['new_id'] 1.042M (± 4.2%) i/s - 5.243M in 5.039858s
user['name'] 1.406M (± 5.6%) i/s - 7.055M in 5.036743s
user['new_name'] 1.074M (± 3.6%) i/s - 5.387M in 5.024152s
```
After (this change):
```
Warming up --------------------------------------
user['id'] 109.775k i/100ms
user['new_id'] 103.303k i/100ms
user['name'] 105.988k i/100ms
user['new_name'] 99.618k i/100ms
Calculating -------------------------------------
user['id'] 1.520M (± 6.7%) i/s - 7.574M in 5.011496s
user['new_id'] 1.485M (± 6.2%) i/s - 7.438M in 5.036252s
user['name'] 1.538M (± 5.4%) i/s - 7.737M in 5.049765s
user['new_name'] 1.516M (± 4.6%) i/s - 7.571M in 5.007293s
```
|
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | | |
Follow up of #35838.
And also this refactors `in_clause_length` handling is entirely
integrated in Arel visitor.
|
| | | | |
| | | | |
| | | | |
| | | | |
| | | | | |
* Avoid duplicated `@new_record` assignment
* Extract `define_attribute_methods` into `init_internals`
|
| | | | |
| | | | |
| | | | |
| | | | | |
Follow up to b1458218c95d85c4ce911dd3e99da5ae7cf7aeee.
|
|/ / / / |
|
|\ \ \ \
| | | | |
| | | | |
| | | | |
| | | | | |
Minor API doc fix
[ci skip]
|
| | |_|/
| |/| | |
|
|/ / / |
|
| | | |
|
|\ \ \
| | | |
| | | | |
Deprecate `where.not` working as NOR and will be changed to NAND in Rails 6.1
|
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | | |
`where.not` with polymorphic association is partly fixed incidentally at
213796f (refer #33493, #26207, #17010, #16983, #14161), and I've added
test case e9ba12f to avoid lose that fix accidentally in the future.
In Rails 5.2, `where.not(polymorphic: object)` works as expected as
NAND, but `where.not(polymorphic_type: object.class.polymorphic_name,
polymorphic_id: object.id)` still unexpectedly works as NOR.
To will make `where.not` working desiredly as NAND in Rails 6.1, this
deprecates `where.not` working as NOR. If people want to continue NOR
conditions, we'd encourage to them to `where.not` each conditions
manually.
```ruby
all = [treasures(:diamond), treasures(:sapphire), cars(:honda), treasures(:sapphire)]
assert_equal all, PriceEstimate.all.map(&:estimate_of)
```
In Rails 6.0:
```ruby
sapphire = treasures(:sapphire)
nor = all.reject { |e|
e.estimate_of_type == sapphire.class.polymorphic_name
}.reject { |e|
e.estimate_of_id == sapphire.id
}
assert_equal [cars(:honda)], nor
without_sapphire = PriceEstimate.where.not(
estimate_of_type: sapphire.class.polymorphic_name, estimate_of_id: sapphire.id
)
assert_equal nor, without_sapphire.map(&:estimate_of)
```
In Rails 6.1:
```ruby
sapphire = treasures(:sapphire)
nand = all - [sapphire]
assert_equal [treasures(:diamond), cars(:honda)], nand
without_sapphire = PriceEstimate.where.not(
estimate_of_type: sapphire.class.polymorphic_name, estimate_of_id: sapphire.id
)
assert_equal nand, without_sapphire.map(&:estimate_of)
```
Resolves #31209.
|
| | | | |
|
| | | |
| | | |
| | | |
| | | | |
Related 0ee96d13de29680e148ccb8e5b68025f29fd091c.
|
|\ \ \ \
| | | | |
| | | | | |
PERF: 20% faster pk attribute access
|
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | | |
I've realized that `user.id` is 20% slower than `user.name` in the
benchmark (https://github.com/rails/rails/pull/35987#issuecomment-483882480).
The reason that performance difference is that `self.class.primary_key`
method call is a bit slow.
Avoiding that method call will make almost attribute access faster and
`user.id` will be completely the same performance with `user.name`.
Before (02b5b8cb):
```
Warming up --------------------------------------
user.id 140.535k i/100ms
user['id'] 96.549k i/100ms
user.name 158.110k i/100ms
user['name'] 94.507k i/100ms
user.changed? 19.003k i/100ms
user.saved_changes? 25.404k i/100ms
Calculating -------------------------------------
user.id 2.231M (± 0.9%) i/s - 11.243M in 5.040066s
user['id'] 1.310M (± 1.3%) i/s - 6.565M in 5.012607s
user.name 2.683M (± 1.2%) i/s - 13.439M in 5.009392s
user['name'] 1.322M (± 0.9%) i/s - 6.615M in 5.003239s
user.changed? 201.999k (±10.9%) i/s - 1.007M in 5.091195s
user.saved_changes? 258.214k (±17.1%) i/s - 1.245M in 5.007421s
```
After (this change):
```
Warming up --------------------------------------
user.id 158.364k i/100ms
user['id'] 106.412k i/100ms
user.name 158.644k i/100ms
user['name'] 107.518k i/100ms
user.changed? 19.082k i/100ms
user.saved_changes? 24.886k i/100ms
Calculating -------------------------------------
user.id 2.768M (± 1.1%) i/s - 13.936M in 5.034957s
user['id'] 1.507M (± 2.1%) i/s - 7.555M in 5.017211s
user.name 2.727M (± 1.5%) i/s - 13.643M in 5.004766s
user['name'] 1.521M (± 1.3%) i/s - 7.634M in 5.018321s
user.changed? 200.865k (±11.1%) i/s - 992.264k in 5.044868s
user.saved_changes? 269.652k (±10.5%) i/s - 1.344M in 5.077972s
```
|
|/ / / /
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | | |
Those helper methods makes relation values access 15% slower.
https://gist.github.com/kamipo/e64439f7a206e1c5b5c69d92d982828e
Before (02b5b8cb):
```
Warming up --------------------------------------
#limit_value 237.074k i/100ms
#limit_value = 1 222.052k i/100ms
Calculating -------------------------------------
#limit_value 6.477M (± 2.9%) i/s - 32.479M in 5.019475s
#limit_value = 1 5.297M (± 4.3%) i/s - 26.424M in 4.999933s
```
After (this change):
```
Warming up --------------------------------------
#limit_value 261.109k i/100ms
#limit_value = 1 239.646k i/100ms
Calculating -------------------------------------
#limit_value 7.412M (± 1.6%) i/s - 37.077M in 5.003345s
#limit_value = 1 6.134M (± 1.0%) i/s - 30.675M in 5.000908s
```
|