Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #4274 (closed enhancement: duplicate)

Opened 3 years ago

Last modified 2 years ago

[PATCH] ActiveRecord: access to numeric/decimal values in PostgreSQL, SQL Server and MySQL as BigDecimals

Reported by: work@ashleymoran.me.uk Assigned to: David
Priority: normal Milestone:
Component: ActiveRecord Version: 1.0.0
Severity: normal Keywords:
Cc: bitsweat

Description (Last modified by nzkoz)

This is my first contribution to Rails and I've only been using Ruby for a few weeks or so, so please forgive me if my code does not follow any conventions.

I am trying to convert my company from C# to Ruby, but since we produce applications almost exclusively for the finance industry I thought the lack of support for SQL decimals would pretty much destroy any chances of that. So I've patched ActiveRecord so you can access decimal types in the database as BigDecimal classes in Ruby.

The CRUD process is the same except you now get BigDecimals back instead of Floats (the exception being that colums defined with no scale, eg decimal(5) are returned as Integers). There is no provision in the patch to override this and return Floats, seeing as I hope one day all adapters will return BigDecimals.

You can also use the type :decimal in migrations to add columns to your database. Specify options :precision and :scale instead of limit, eg

create_table :users do |table|

table.column :my_decimal, :decimal, :precision => 12, :scale => 4

end

will give you a colum numeric(12,4)

Omitting the scale will still give you a numeric field; it will only be converted to an Integer when you read the data back.

(I haven't actually tested this in practice, but the unit tests generate the correct SQL to add the columns)

The patch is against the stable branch, at revision 3901. Let me know if it's acceptable. The changes are restricted to the PostgreSQLAdapter class so they should not affect other databases. If I get time, I'd be willing to work on abstracting the decimal type to work with all adapters.

Attachments

active_record_postgres_decimals.patch (13.2 kB) - added by work@ashleymoran.me.uk on 03/17/06 00:44:36.
patch to let PostgreSQL read and write decimals as BigDecimals
active_record_postgres_decimals_2.patch (18.4 kB) - added by mail@ashleymoran.me.uk on 03/18/06 01:06:33.
BigDecimal patch + miscellaneous bug fixes scattered through the Rails Trac
postgresql_colum_decimals_arrays_domains.patch (18.5 kB) - added by meadow.nnick@gmail.com on 03/18/06 11:36:22.
cumulative modified patch
postgresql_decimals.patch (25.0 kB) - added by work@ashleymoran.me.uk on 03/20/06 22:04:11.
Cumulative patch against rev 4005
big_numbers.rb (72 bytes) - added by work@ashleymoran.me.uk on 03/20/06 22:05:57.
New file to go with postgresql_decimals.patch
1_give_me_big_numbers.rb (500 bytes) - added by work@ashleymoran.me.uk on 03/20/06 22:06:46.
New file to go with postgresql_decimals.patch
postgresql_decimals_2.patch (26.0 kB) - added by work@ashleymoran.me.uk on 03/21/06 00:00:13.
Cumulative patch against revision 4005 (minor SchemaDumper bug fixed from previous patch)
postgresql_decimals_3.patch (27.7 kB) - added by work@ashleymoran.me.uk on 03/23/06 19:54:49.
Another minor change - this time for SchemaDumper to work around BigDecimal#inspect not returning a readable string
postgresql_decimals_4.patch (27.8 kB) - added by work@ashleymoran.me.uk on 03/23/06 20:06:14.
meh... nice to check we are actually using the postgres adapter before testing it
postgresql_sqlserver_decimals.patch (42.1 kB) - added by work@ashleymoran.me.uk on 03/25/06 18:16:33.
Includes all fixes mentioned for Postgres, plus BigDecimal support in SQL Server
postgresql_sqlserver_decimals_2.sql (44.4 kB) - added by work@ashleymoran.me.uk on 03/26/06 11:41:16.
forgot to include SQLServer in some of the tests
active_record_decimals_pg_mssql_mysql.patch (51.9 kB) - added by work@ashleymoran.me.uk on 04/11/06 22:40:28.
active_record_decimals_pg_mssql_mysql_2.patch (52.3 kB) - added by work@ashleymoran.me.uk on 04/12/06 19:38:34.
Same as previous patch, except with the minor fix to SchemaDumper

Change History

03/17/06 00:44:36 changed by work@ashleymoran.me.uk

  • attachment active_record_postgres_decimals.patch added.

patch to let PostgreSQL read and write decimals as BigDecimals

03/17/06 00:46:02 changed by work@ashleymoran.me.uk

sorry I didn't know there was an attach button on the next page!!! someone might want to trim that down :)

03/17/06 04:48:14 changed by nzkoz

  • description changed.

03/17/06 06:52:34 changed by meadow.nnick@gmail.com

I haven't yet tried your patch, only skimmed through code and here are some suggestions:

1. I suspect that following lines can throw an exception when using latest ruby-postgres driver, as numerics will already be returned as BigDecimals

369 	+                elsif res.type(cel_index) == NUMERIC_COLUMN_TYPE_OID
370 	+                  column = column.to_d

2. Precision and scale for numerics can also be extracted from typmod

 if(typmod != -1) {
        tmp_typmod = typmod - 4;
        precision = (tmp_typmod >> 16) & 0xffff;
        scale = tmp_typmod & 0xffff;
        maxdigits = precision - scale;
 }

see http://dev.rubyonrails.org/attachment/ticket/3278/postgresql-column.patch#L113 for SQL query to get typmod for table attributes.

3. If you are going to add PostgreSQLColumn , take also look at ticket #3278, maybe elaborate more complete solution?

03/18/06 01:05:32 changed by mail@ashleymoran.me.uk

Actually I had considered a test before calling to_d but removed it. My first solution was actually to re-open BigDecimal and define to_d. It seems bizarre that BigDecimal#to_d does not exist, given that Integer#to_i (etc) does.

Thanks for the pointer on typmod. I can't completely eliminate dependency on the SQL type string though, because of my decision to return integers for decimals with scale 0. (Is this desireable? Or would it be better just to return BigDecimals in all cases?)

I followed up #3278 and incorporated all the changes. Again here is another patch against the stable branch (since that passes all tests). I haven't added in tests for the fixes in your ticket because my main concern is the decimal support. Was there anything else you were thinking of to make this a complete solution?

03/18/06 01:06:33 changed by mail@ashleymoran.me.uk

  • attachment active_record_postgres_decimals_2.patch added.

BigDecimal patch + miscellaneous bug fixes scattered through the Rails Trac

03/18/06 11:36:22 changed by meadow.nnick@gmail.com

  • attachment postgresql_colum_decimals_arrays_domains.patch added.

cumulative modified patch

03/18/06 11:44:44 changed by meadow.nnick@gmail.com

This patch is against revision [3920], incorporates code from work@ashleymoran.me.uk and #3278 with some modifications.

Summary of changes:

* added PostgreSQLColumn;

* added support for numeric types - type cast as BigDecimal;

* arrays are treated as Strings;

* domain types are recognized as base types - column info query has changed;

* limits and precision/scale(for numeric) for columns are extracted from typmod;

* Date/Time magic default values (now()) are discarded

This patch also added test to check that SQL domains are correctly recognized as base types.

Note on tests with ruby-postgres-20051221:

1. With PGconn.translate_results=false, one failure, related to bytea unescaping:

test_load_save(BinaryTest) (/test/binary_test.rb)

2. With PGconn.translate_results=true - 9 failures and 1 error, most of them on the same reason:

expected to be kind_of? <Time> but was <DateTime>.

03/18/06 11:50:03 changed by meadow.nnick@gmail.com

Actually I had considered a test before calling to_d but removed it.

In my patch I changed it to:

elsif res.type(cel_index) == NUMERIC_COLUMN_TYPE_OID && column.kind_of?(String)
# if this isn't string, then driver probably already converted it
  column = column.to_d
end

I can't completely eliminate dependency on the SQL type string though, because of my decision to return integers for decimals with scale 0.

def translate_postgres_type(postgres_type, typmod = -1)
#.... snip...
          case postgres_type
            when /^(decimal|numeric)/i
              (typmod != -1) && ( 0 == (typmod-4) & 0xFFFF) ? :integer : :decimal

03/18/06 13:47:05 changed by work@ashleymoran.me.uk

Looking good. The new adapter passes all the tests in Rails 1.0 bar a few that have been pulled since. Now I can actually produce a prototype app which I planned to start a week ago... before I got slightly waylayed by the lack of decimals!!!

Is there anything else that needs doing to the postgres adapter? I saw at http://www.height1percent.com/articles/category/postgresql that biginteger/serial might be deseriable. It shouldn't be too hard but it's non portable.

Also I have access to a SQL Server database through work - although hopefully for not much longer =) I know of one person stuck using that who would like decimal support. Is it worth me having a go? Perhaps it will help push decimal support across the board.

03/19/06 23:38:08 changed by work@ashleymoran.me.uk

Just noticed your comments about the native ruby driver test failures. Have they been introduced with the new changes in the patch? I never tried the pure ruby adapter because there is no connection setup for it in the trunk

Also I've finally written a migration and I've noticed that if you specify :decimal in a create_table eg

create_table :user_vehicle_options do |table|
  table.column :price, :decimal, :precision => 9, :scale => 2
end

you get a plain numeric - the precision and scale are ignored. I can't see why at a quick glance - the SQL comes out fine for the standalone add_column command.

03/20/06 07:27:48 changed by meadow.nnick@gmail.com

ruby-postgres is Ruby extension using the libpq C library. Those faults were introduced first with november development snapshots of driver (http://rubyforge.org/frs/?group_id=636) and aren't related to this patch.

To provide full support for DECIMAL type in migrations you should dive into code of active_record/migration.rb and active_record/connection_adapters/abstract/schema_definitions.rb

For now, migrations with

add_column :user_vehicle_options, :price, :decimal, :precision => 9, :scale => 2

should work fine.

As for me, I just don't use migrations, and prefer plain SQL, so I don't care :)

03/20/06 22:03:01 changed by work@ashleymoran.me.uk

meadow.nnick-

I'm a big fan of continuous integration and repeatable database migrations, so I really wanted to make migrations work elegantly for me. I followed your pointer and actually the changes required to make migrations work are minimal, BUT they do impact the abstract adapter. Basically it comes down to an updated ColumnDefinition:

    class ColumnDefinition < Struct.new(:base, :name, :type, :limit, :precision, :scale, :default, :null) #:nodoc:
      def to_sql
        column_sql = "#{base.quote_column_name(name)} #{type_to_sql(type.to_sym, limit, precision, scale)}"
        add_column_options!(column_sql, :null => null, :default => default)
        column_sql
      end
      alias to_s :to_sql

      private
        def type_to_sql(name, limit, precision, scale)
          if base.native_database_types.has_key?(:decimal)
            base.type_to_sql(name, limit, precision, scale) rescue name
          else
            base.type_to_sql(name, limit) rescue name
          end
        end   
        
        <snip>
    end

I also modifed the PostgreSQL adapter to fit with this. I guess the fact that these changes affect the core ActiveRecord classes makes them less likely to get accepted (at least any time soon). But it seems quite cleanly implemented - at least to my eyes.

I haven't tested the updated patch against non-PostgreSQL databases because I don't have time right now to install MySQL (and I can only get at SQL Server at work). Should work though, I hope.

The postgresql_decimals.patch is a cumulative patch against rev 4005 containing everything we've looked at, plus a bit of tidying up because I'm a perfectionist :) Also I've attached two new files to go in activerecord/test/fixtures: big_numbers.rb and migrations_with_decimal/1_give_me_big_numbers.rb which work with the new migration test case.

03/20/06 22:04:11 changed by work@ashleymoran.me.uk

  • attachment postgresql_decimals.patch added.

Cumulative patch against rev 4005

03/20/06 22:05:57 changed by work@ashleymoran.me.uk

  • attachment big_numbers.rb added.

New file to go with postgresql_decimals.patch

03/20/06 22:06:46 changed by work@ashleymoran.me.uk

  • attachment 1_give_me_big_numbers.rb added.

New file to go with postgresql_decimals.patch

03/20/06 23:59:16 changed by work@ashleymoran.me.uk

Ooops forgot to require bigdecimal/util in the adapter, so SchemaDumper failed if you had a default contstrain in a migration. Fixed patch attached as postgresql_decimals_2.patch

03/21/06 00:00:13 changed by work@ashleymoran.me.uk

  • attachment postgresql_decimals_2.patch added.

Cumulative patch against revision 4005 (minor SchemaDumper bug fixed from previous patch)

03/23/06 19:54:49 changed by work@ashleymoran.me.uk

  • attachment postgresql_decimals_3.patch added.

Another minor change - this time for SchemaDumper to work around BigDecimal#inspect not returning a readable string

03/23/06 20:06:14 changed by work@ashleymoran.me.uk

  • attachment postgresql_decimals_4.patch added.

meh... nice to check we are actually using the postgres adapter before testing it

03/25/06 18:15:06 changed by work@ashleymoran.me.uk

  • summary changed from [PATCH] ActiveRecord: creation of and access to numeric/decimal values in PostgreSQL as BigDecimals to [PATCH] ActiveRecord: creation of and access to numeric/decimal values in PostgreSQL and SQL Server as BigDecimals.

I've managed to get the BigDecimal patch for PostgreSQL working for SQL Server too. (Tested against SQL Server 2000, on Mac OS X 10.4.5 with unixODBC and FreeTDS (latest versions from DarwinPorts - I can't say if it will work with ADO connections and I don't have access to a Windows machine to test it).

If I get time I might have a go at MySQL or Firebird (probably Firebird as the precision and scale are already available in the adapter). If I manage all four (PG, MS, MY, FB) I'll try to factor out the decimal code into the abstract adapter, without breaking everything. I don't have access to any other commercial databases so someone else will have to do the rest.


Note: the following tests are failing on my setup but are unrelated to this patch, as I ran the full test suite before I started working:

  1) Failure:
test_polymorphic_has_many_going_through_join_model_with_disabled_include(AssociationsJoinModelTest)
    [./test/abstract_unit.rb:44:in `assert_queries'
     ./test/associations_join_model_test.rb:68:in `test_polymorphic_has_many_going_through_join_model_with_disabled_include']:
0 instead of 1 queries were executed.
<1> expected but was
<0>.

  2) Error:
test_assert_queries(BasicsTest):
ActiveRecord::StatementInvalid: DBI::DatabaseError: 24000 (0) [unixODBC][FreeTDS][SQL Server]Invalid cursor state: select count(*) from developers
    ./test/../lib/active_record/connection_adapters/abstract_adapter.rb:120:in `log'
    ./test/../lib/active_record/connection_adapters/sqlserver_adapter.rb:347:in `execute_without_query_counting'
    ./test/abstract_unit.rb:62:in `execute'
    ./test/base_test.rb:1244:in `test_assert_queries'
    ./test/base_test.rb:1245:in `test_assert_queries'
    ./test/base_test.rb:1245:in `test_assert_queries'
    ./test/abstract_unit.rb:39:in `assert_queries'
    ./test/base_test.rb:1245:in `test_assert_queries'

03/25/06 18:16:33 changed by work@ashleymoran.me.uk

  • attachment postgresql_sqlserver_decimals.patch added.

Includes all fixes mentioned for Postgres, plus BigDecimal support in SQL Server

03/26/06 11:41:16 changed by work@ashleymoran.me.uk

  • attachment postgresql_sqlserver_decimals_2.sql added.

forgot to include SQLServer in some of the tests

04/11/06 22:39:56 changed by work@ashleymoran.me.uk

Someone on the rails list expressed an interest in decimal support for MySQL so I ported the patch tonight, using 4.1.18 as a test server. It was scarily easy - there are only two lines changed to add decimal support! I even went breaking some tests to be sure they are actually running.

I've noticed an omission in my code, namely that the schema dumper does not output the precision and scale like it does the limit and default. However this should be another two line change that I'll do when I get time.

I took two liberties with the other code at the same time: I factored out all the decimal stuff into the abstract adapter, and I cleaned up some duplication of the abstract code in the SQL Server adapter. Some tests are failing for MSSQL but I am pretty sure they are unrelated to the decimal support. Unfortunately ( fortunately? :) ) my only accessto SQL Server at home is over a DSL link and the tests take 20 mins to run, so I'm not inclined to look into that just yet...

This is 3 out of the big 4 databases done (PG, MS, My, Oracle remaining). I could do Firebird but nobody has shown any interest in that. I don't have access to Oracle so someone else will have to make the patch work for that. It should be easy with the decimal code being in the abstract class though.

04/11/06 22:40:28 changed by work@ashleymoran.me.uk

  • attachment active_record_decimals_pg_mssql_mysql.patch added.

04/11/06 22:42:31 changed by work@ashleymoran.me.uk

  • summary changed from [PATCH] ActiveRecord: creation of and access to numeric/decimal values in PostgreSQL and SQL Server as BigDecimals to [PATCH] ActiveRecord: access to numeric/decimal values in PostgreSQL, SQL Server and MySQL as BigDecimals.

just changing the ticket title :)

04/12/06 19:38:34 changed by work@ashleymoran.me.uk

  • attachment active_record_decimals_pg_mssql_mysql_2.patch added.

Same as previous patch, except with the minor fix to SchemaDumper

04/14/06 14:24:11 changed by greggh@geekhousecalls.com

This seems to be working for me. I will test it out more and get back to you, but so far so good. This really should go into core.

06/18/06 08:57:10 changed by robbat2@gentoo.org

I'm working on merging this with my submission in ticket 4605. One problem I have found so far is inconsistent behaviors on high precision numbers - your code fails completely at them, with float behavior in many places. At low precision, everything is fine, but up at high precision, it's getting nasty.

I'm trying to collect input over a few databases for these nasty cases: http://robbat2.livejournal.com/200033.html

I already have Postgres/MySQL/SQLite{2,3}/SQLServer.

Patch to be posted tommorow.

06/20/06 19:06:34 changed by bitsweat

  • cc set to bitsweat.

06/21/06 04:28:19 changed by robbat2@gentoo.org

Please close this ticket, and mark as a duplicate of 5454, which contains my merged patch.

06/21/06 07:00:05 changed by bitsweat

  • status changed from new to closed.
  • resolution set to duplicate.

Thank you. Superseded by #5454.

07/24/06 03:09:41 changed by anonymous

jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue jiaxue