initial commit

This commit is contained in:
Manuel Kniep
2015-07-14 14:13:11 +02:00
parent 2516d369e5
commit 58f97dc248
14 changed files with 734 additions and 0 deletions

4
Gemfile Normal file
View File

@@ -0,0 +1,4 @@
# Gemfile
source 'https://rubygems.org'
gem 'dumbo', :path => "/Users/rapimo/adjust/dumbo"
gem 'pry'

69
Gemfile.lock Normal file
View File

@@ -0,0 +1,69 @@
PATH
remote: /Users/rapimo/adjust/dumbo
specs:
dumbo (0.0.4)
activerecord
activesupport
bundler (~> 1.5)
erubis
pg (> 0.17)
pry
rake
rspec (~> 3.0.0)
thor
GEM
remote: https://rubygems.org/
specs:
activemodel (4.2.3)
activesupport (= 4.2.3)
builder (~> 3.1)
activerecord (4.2.3)
activemodel (= 4.2.3)
activesupport (= 4.2.3)
arel (~> 6.0)
activesupport (4.2.3)
i18n (~> 0.7)
json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
arel (6.0.0)
builder (3.2.2)
coderay (1.1.0)
diff-lcs (1.2.5)
erubis (2.7.0)
i18n (0.7.0)
json (1.8.3)
method_source (0.8.2)
minitest (5.7.0)
pg (0.18.2)
pry (0.10.1)
coderay (~> 1.1.0)
method_source (~> 0.8.1)
slop (~> 3.4)
rake (10.4.2)
rspec (3.0.0)
rspec-core (~> 3.0.0)
rspec-expectations (~> 3.0.0)
rspec-mocks (~> 3.0.0)
rspec-core (3.0.4)
rspec-support (~> 3.0.0)
rspec-expectations (3.0.4)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.0.0)
rspec-mocks (3.0.4)
rspec-support (~> 3.0.0)
rspec-support (3.0.4)
slop (3.6.0)
thor (0.19.1)
thread_safe (0.3.5)
tzinfo (1.2.2)
thread_safe (~> 0.1)
PLATFORMS
ruby
DEPENDENCIES
dumbo!
pry

12
Makefile Normal file
View File

@@ -0,0 +1,12 @@
#http://blog.pgxn.org/post/4783001135/extension-makefiles pg makefiles
EXTENSION = base36
PG_CONFIG ?= pg_config
DATA = $(wildcard *--*.sql)
PGXS := $(shell $(PG_CONFIG) --pgxs)
MODULE_big = base36
OBJS = $(patsubst %.c,%.o,$(wildcard src/*.c))
TESTS = $(wildcard test/sql/*.sql)
REGRESS = $(patsubst test/sql/%.sql,%,$(TESTS))
REGRESS_OPTS = --inputdir=test --load-language=plpgsql
include $(PGXS)

15
Rakefile Normal file
View File

@@ -0,0 +1,15 @@
require 'rake'
require 'rspec/core/rake_task'
require 'dumbo/rake_task'
require 'dumbo/db_task'
require 'dumbo'
load File.expand_path('../config/boot.rb', __FILE__)
Dumbo::RakeTask.new(:dumbo)
Dumbo::DbTask.new(:db)
RSpec::Core::RakeTask.new(:spec)
Dir.glob('lib/tasks/**/*.rake').each { |taskfile| load taskfile }
task default: ['dumbo:all', 'db:test:prepare', :spec]

152
base36--0.0.1.sql Normal file
View File

@@ -0,0 +1,152 @@
--source file sql/base36.sql
CREATE FUNCTION base36_in(cstring)
RETURNS base36
AS '$libdir/base36'
LANGUAGE C IMMUTABLE STRICT;
CREATE FUNCTION base36_out(base36)
RETURNS cstring
AS '$libdir/base36'
LANGUAGE C IMMUTABLE STRICT;
CREATE FUNCTION base36_recv(internal)
RETURNS base36
AS '$libdir/base36'
LANGUAGE C IMMUTABLE STRICT;
CREATE FUNCTION base36_send(base36)
RETURNS bytea
AS '$libdir/base36'
LANGUAGE C IMMUTABLE STRICT;
CREATE TYPE base36 (
INPUT = base36_in,
OUTPUT = base36_out,
RECEIVE = base36_recv,
SEND = base36_send,
LIKE = bigint,
CATEGORY = 'N'
);
COMMENT ON TYPE base36 IS 'bigint written in base36: [0-9A-Z]+';
CREATE FUNCTION base36(text)
RETURNS base36
AS '$libdir/base36', 'base36_cast_from_text'
LANGUAGE C IMMUTABLE STRICT;
CREATE FUNCTION text(base36)
RETURNS text
AS '$libdir/base36', 'base36_cast_to_text'
LANGUAGE C IMMUTABLE STRICT;
CREATE CAST (text as base36) WITH FUNCTION base36(text) AS IMPLICIT;
CREATE CAST (base36 as text) WITH FUNCTION text(base36);
CREATE CAST (bigint as base36) WITHOUT FUNCTION AS IMPLICIT;
CREATE CAST (base36 as bigint) WITHOUT FUNCTION AS IMPLICIT;
CREATE FUNCTION base36_eq(base36, base36)
RETURNS boolean LANGUAGE internal IMMUTABLE AS 'int8eq';
CREATE FUNCTION base36_ne(base36, base36)
RETURNS boolean LANGUAGE internal IMMUTABLE AS 'int8ne';
CREATE FUNCTION base36_lt(base36, base36)
RETURNS boolean LANGUAGE internal IMMUTABLE AS 'int8lt';
CREATE FUNCTION base36_le(base36, base36)
RETURNS boolean LANGUAGE internal IMMUTABLE AS 'int8le';
CREATE FUNCTION base36_gt(base36, base36)
RETURNS boolean LANGUAGE internal IMMUTABLE AS 'int8gt';
CREATE FUNCTION base36_ge(base36, base36)
RETURNS boolean LANGUAGE internal IMMUTABLE AS 'int8ge';
CREATE FUNCTION base36_cmp(base36, base36)
RETURNS integer LANGUAGE internal IMMUTABLE AS 'btint8cmp';
CREATE FUNCTION hash_base36(base36)
RETURNS integer LANGUAGE internal IMMUTABLE AS 'hashint8';
CREATE OPERATOR = (
LEFTARG = base36,
RIGHTARG = base36,
PROCEDURE = base36_eq,
COMMUTATOR = '=',
NEGATOR = '<>',
RESTRICT = eqsel,
JOIN = eqjoinsel
);
COMMENT ON OPERATOR =(base36, base36) IS 'equals?';
CREATE OPERATOR <> (
LEFTARG = base36,
RIGHTARG = base36,
PROCEDURE = base36_ne,
COMMUTATOR = '<>',
NEGATOR = '=',
RESTRICT = neqsel,
JOIN = neqjoinsel
);
COMMENT ON OPERATOR <>(base36, base36) IS 'not equals?';
CREATE OPERATOR < (
LEFTARG = base36,
RIGHTARG = base36,
PROCEDURE = base36_lt,
COMMUTATOR = > ,
NEGATOR = >= ,
RESTRICT = scalarltsel,
JOIN = scalarltjoinsel
);
COMMENT ON OPERATOR <(base36, base36) IS 'less-than';
CREATE OPERATOR <= (
LEFTARG = base36,
RIGHTARG = base36,
PROCEDURE = base36_le,
COMMUTATOR = >= ,
NEGATOR = > ,
RESTRICT = scalarltsel,
JOIN = scalarltjoinsel
);
COMMENT ON OPERATOR <=(base36, base36) IS 'less-than-or-equal';
CREATE OPERATOR > (
LEFTARG = base36,
RIGHTARG = base36,
PROCEDURE = base36_gt,
COMMUTATOR = < ,
NEGATOR = <= ,
RESTRICT = scalargtsel,
JOIN = scalargtjoinsel
);
COMMENT ON OPERATOR >(base36, base36) IS 'greater-than';
CREATE OPERATOR >= (
LEFTARG = base36,
RIGHTARG = base36,
PROCEDURE = base36_ge,
COMMUTATOR = <= ,
NEGATOR = < ,
RESTRICT = scalargtsel,
JOIN = scalargtjoinsel
);
COMMENT ON OPERATOR >=(base36, base36) IS 'greater-than-or-equal';
CREATE OPERATOR CLASS btree_base36_ops
DEFAULT FOR TYPE base36 USING btree
AS
OPERATOR 1 < ,
OPERATOR 2 <= ,
OPERATOR 3 = ,
OPERATOR 4 >= ,
OPERATOR 5 > ,
FUNCTION 1 base36_cmp(base36, base36);
CREATE OPERATOR CLASS hash_base36_ops
DEFAULT FOR TYPE base36 USING hash AS
OPERATOR 1 = ,
FUNCTION 1 hash_base36(base36);

5
base36.control Normal file
View File

@@ -0,0 +1,5 @@
# base36 extension
comment = 'my awesome extension'
default_version = '0.0.1'
relocatable = true
requires = ''

14
config/boot.rb Normal file
View File

@@ -0,0 +1,14 @@
$LOAD_PATH.unshift File.expand_path('../..', __FILE__)
# Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
ENV['DUMBO_ENV'] ||= 'development'
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
Bundler.require(:default, ENV['DUMBO_ENV'].to_sym)
def db_config
@config ||= YAML.load_file('config/database.yml')
end
ActiveRecord::Base.establish_connection db_config[ENV['DUMBO_ENV']]

31
config/database.yml Normal file
View File

@@ -0,0 +1,31 @@
# general postgres settings
# Connect on a TCP socket. Omitted by default since the client uses a
# domain socket that doesn't need configuration. Windows does not have
# domain sockets, so uncomment these lines.
# host: localhost
# port: 5432
# Schema search path. The server defaults to $user,public
# schema_search_path: myapp,sharedapp,public
# Minimum log levels, in increasing order:
# debug5, debug4, debug3, debug2, debug1,
# log, notice, warning, error, fatal, and panic
# The server defaults to notice.
# min_messages: warning
postgres: &postgres
adapter: postgresql
encoding: utf8
pool: 5
username: postgres
password:
host: localhost
development:
<<: *postgres
database: base36_development
test:
<<: *postgres
database: base36_test

47
spec/base36_spec.rb Normal file
View File

@@ -0,0 +1,47 @@
require 'spec_helper'
describe 'base36' do
before do
install_extension
end
describe 'conversion' do
it 'should encode integer' do
query('SELECT 120::bigint::base36').should match '3c'
end
it 'should decode text' do
query("SELECT '3c'::base36::bigint").should match '120'
end
it 'should error to big values' do
expect{query("SELECT '3caaaaaaaaaaaaa'::base36::bigint")}.to throw_error 'value "3caaaaaaaaaaaaa" is out of range for type base36'
end
it 'should cast big base36 values' do
query("SELECT '1y2p0ij32e8e7'::base36::bigint").should match 9223372036854775807
end
it 'should cast big int values' do
query("SELECT 9223372036854775807::base36").should match '1y2p0ij32e8e7'
end
it 'should error to big values' do
expect{query("SELECT '1y2p0ij32e8e8'::base36::bigint")}.to throw_error 'value "1y2p0ij32e8e8" is out of range for type base36'
end
it 'should error invalid values' do
expect{query("SELECT '3&'::base36::bigint")}.to throw_error 'value "&" is not a valid digit for type base36'
end
end
describe 'comparison' do
it 'should compare using >' do
query("SELECT 'a'::base36 > 'b'::base36").should match 'f'
end
it 'should compare using <' do
query("SELECT 'a'::base36 < 'b'::base36").should match 't'
end
end
end

34
spec/spec_helper.rb Normal file
View File

@@ -0,0 +1,34 @@
require 'rubygems'
require 'dumbo/test'
ENV['DUMBO_ENV'] ||= 'test'
require File.expand_path('../../config/boot', __FILE__)
require 'dumbo/test/silence_unknown_oid'
ActiveRecord::Base.logger.level = 0 if ActiveRecord::Base.logger
Dir.glob('spec/support/**/*.rb').each { |f| require f }
RSpec.configure do |config|
config.fail_fast = false
config.order = 'random'
config.filter_run focus: true
config.run_all_when_everything_filtered = true
config.expect_with :rspec do |c|
c.syntax = [:should, :expect]
end
# wrap test in transactions
config.around(:each) do |example|
ActiveRecord::Base.transaction do
example.run
fail ActiveRecord::Rollback
end
end
config.include(Dumbo::Test::Helper)
config.include(Dumbo::Matchers)
end
require 'dumbo/test/regression_helper' if ENV['DUMBO_REGRESSION']

View File

@@ -0,0 +1,31 @@
module ExtensionHelper
def install_testing_extension
system <<-CMD
(
mkdir -p #{spec_root}/dumbo_sample_runtime && \
cp -R #{spec_root}/dumbo_sample/ #{spec_root}/dumbo_sample_runtime
cd #{spec_root}/dumbo_sample_runtime && \
make clean && \
make && \
make install
) 1> /dev/null
CMD
end
def uninstall_testing_extension
system <<-CMD
(
cd #{spec_root}/dumbo_sample_runtime && \
make clean && \
make uninstall && \
rm -rf #{spec_root}/dumbo_sample_runtime
) 1> /dev/null
CMD
end
private
def spec_root
File.join(File.dirname(__FILE__), '..')
end
end

150
sql/base36.sql Normal file
View File

@@ -0,0 +1,150 @@
CREATE FUNCTION base36_in(cstring)
RETURNS base36
AS '$libdir/base36'
LANGUAGE C IMMUTABLE STRICT;
CREATE FUNCTION base36_out(base36)
RETURNS cstring
AS '$libdir/base36'
LANGUAGE C IMMUTABLE STRICT;
CREATE FUNCTION base36_recv(internal)
RETURNS base36
AS '$libdir/base36'
LANGUAGE C IMMUTABLE STRICT;
CREATE FUNCTION base36_send(base36)
RETURNS bytea
AS '$libdir/base36'
LANGUAGE C IMMUTABLE STRICT;
CREATE TYPE base36 (
INPUT = base36_in,
OUTPUT = base36_out,
RECEIVE = base36_recv,
SEND = base36_send,
LIKE = bigint,
CATEGORY = 'N'
);
COMMENT ON TYPE base36 IS 'bigint written in base36: [0-9A-Z]+';
CREATE FUNCTION base36(text)
RETURNS base36
AS '$libdir/base36', 'base36_cast_from_text'
LANGUAGE C IMMUTABLE STRICT;
CREATE FUNCTION text(base36)
RETURNS text
AS '$libdir/base36', 'base36_cast_to_text'
LANGUAGE C IMMUTABLE STRICT;
CREATE CAST (text as base36) WITH FUNCTION base36(text) AS IMPLICIT;
CREATE CAST (base36 as text) WITH FUNCTION text(base36);
CREATE CAST (bigint as base36) WITHOUT FUNCTION AS IMPLICIT;
CREATE CAST (base36 as bigint) WITHOUT FUNCTION AS IMPLICIT;
CREATE FUNCTION base36_eq(base36, base36)
RETURNS boolean LANGUAGE internal IMMUTABLE AS 'int8eq';
CREATE FUNCTION base36_ne(base36, base36)
RETURNS boolean LANGUAGE internal IMMUTABLE AS 'int8ne';
CREATE FUNCTION base36_lt(base36, base36)
RETURNS boolean LANGUAGE internal IMMUTABLE AS 'int8lt';
CREATE FUNCTION base36_le(base36, base36)
RETURNS boolean LANGUAGE internal IMMUTABLE AS 'int8le';
CREATE FUNCTION base36_gt(base36, base36)
RETURNS boolean LANGUAGE internal IMMUTABLE AS 'int8gt';
CREATE FUNCTION base36_ge(base36, base36)
RETURNS boolean LANGUAGE internal IMMUTABLE AS 'int8ge';
CREATE FUNCTION base36_cmp(base36, base36)
RETURNS integer LANGUAGE internal IMMUTABLE AS 'btint8cmp';
CREATE FUNCTION hash_base36(base36)
RETURNS integer LANGUAGE internal IMMUTABLE AS 'hashint8';
CREATE OPERATOR = (
LEFTARG = base36,
RIGHTARG = base36,
PROCEDURE = base36_eq,
COMMUTATOR = '=',
NEGATOR = '<>',
RESTRICT = eqsel,
JOIN = eqjoinsel
);
COMMENT ON OPERATOR =(base36, base36) IS 'equals?';
CREATE OPERATOR <> (
LEFTARG = base36,
RIGHTARG = base36,
PROCEDURE = base36_ne,
COMMUTATOR = '<>',
NEGATOR = '=',
RESTRICT = neqsel,
JOIN = neqjoinsel
);
COMMENT ON OPERATOR <>(base36, base36) IS 'not equals?';
CREATE OPERATOR < (
LEFTARG = base36,
RIGHTARG = base36,
PROCEDURE = base36_lt,
COMMUTATOR = > ,
NEGATOR = >= ,
RESTRICT = scalarltsel,
JOIN = scalarltjoinsel
);
COMMENT ON OPERATOR <(base36, base36) IS 'less-than';
CREATE OPERATOR <= (
LEFTARG = base36,
RIGHTARG = base36,
PROCEDURE = base36_le,
COMMUTATOR = >= ,
NEGATOR = > ,
RESTRICT = scalarltsel,
JOIN = scalarltjoinsel
);
COMMENT ON OPERATOR <=(base36, base36) IS 'less-than-or-equal';
CREATE OPERATOR > (
LEFTARG = base36,
RIGHTARG = base36,
PROCEDURE = base36_gt,
COMMUTATOR = < ,
NEGATOR = <= ,
RESTRICT = scalargtsel,
JOIN = scalargtjoinsel
);
COMMENT ON OPERATOR >(base36, base36) IS 'greater-than';
CREATE OPERATOR >= (
LEFTARG = base36,
RIGHTARG = base36,
PROCEDURE = base36_ge,
COMMUTATOR = <= ,
NEGATOR = < ,
RESTRICT = scalargtsel,
JOIN = scalargtjoinsel
);
COMMENT ON OPERATOR >=(base36, base36) IS 'greater-than-or-equal';
CREATE OPERATOR CLASS btree_base36_ops
DEFAULT FOR TYPE base36 USING btree
AS
OPERATOR 1 < ,
OPERATOR 2 <= ,
OPERATOR 3 = ,
OPERATOR 4 >= ,
OPERATOR 5 > ,
FUNCTION 1 base36_cmp(base36, base36);
CREATE OPERATOR CLASS hash_base36_ops
DEFAULT FOR TYPE base36 USING hash AS
OPERATOR 1 = ,
FUNCTION 1 hash_base36(base36);

151
src/base36.c Normal file
View File

@@ -0,0 +1,151 @@
#include "base36.h"
PG_MODULE_MAGIC;
#define BASE36_LENGTH 13
static int base36_digits[36] =
{
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z'
};
static base36 base36_powers[BASE36_LENGTH] =
{
1ULL,
36ULL,
1296ULL,
46656ULL,
1679616ULL,
60466176ULL,
2176782336ULL,
78364164096ULL,
2821109907456ULL,
101559956668416ULL,
3656158440062976ULL,
131621703842267136ULL,
4738381338321616896ULL
};
static inline
base36 base36_from_str(const char *str)
{
int i, d = 0, n = strlen(str);
base36 c = 0;
if( n == 0 || n > BASE36_LENGTH )
{
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value \"%s\" is out of range for type base36",
str)));
}
for(i=0; i<n; i++) {
if( str[i] >= '0' && str[i] <= '9' )
d = str[i] - '0';
else if ( str[i] >= 'A' && str[i] <= 'Z' )
d = 10 + str[i] - 'A';
else if ( str[i] >= 'a' && str[i] <= 'z' )
d = 10 + str[i] - 'a';
else
ereport(ERROR, (
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("value \"%c\" is not a valid digit for type base36", str[i])
)
);
c += d * base36_powers[n-i-1];
if ( c < 0 )
{
ereport(ERROR, (
errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("value \"%s\" is out of range for type base36", str)
)
);
}
}
return c;
}
static inline
char *base36_to_str(base36 c)
{
int i, d, p = 0;
base36 m = c;
bool discard = true;
char *str = palloc0((BASE36_LENGTH + 1) * sizeof(char));
for(i=BASE36_LENGTH-1; i>=0; i--)
{
d = m / base36_powers[i];
m = m - base36_powers[i] * d;
discard = discard && (d == 0 && i >0);
if( !discard )
str[p++] = base36_digits[d];
}
return str;
}
PG_FUNCTION_INFO_V1(base36_in);
Datum
base36_in(PG_FUNCTION_ARGS)
{
char *str = PG_GETARG_CSTRING(0);
PG_RETURN_INT64(base36_from_str(str));
}
PG_FUNCTION_INFO_V1(base36_out);
Datum
base36_out(PG_FUNCTION_ARGS)
{
base36 c = PG_GETARG_INT64(0);
PG_RETURN_CSTRING(base36_to_str(c));
}
PG_FUNCTION_INFO_V1(base36_recv);
Datum
base36_recv(PG_FUNCTION_ARGS)
{
StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
const char *str = pq_getmsgstring(buf);
pq_getmsgend(buf);
PG_RETURN_INT64(base36_from_str(str));
}
PG_FUNCTION_INFO_V1(base36_send);
Datum
base36_send(PG_FUNCTION_ARGS)
{
base36 c = PG_GETARG_INT64(0);
StringInfoData buf;
pq_begintypsend(&buf);
pq_sendstring(&buf, base36_to_str(c));
PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
}
PG_FUNCTION_INFO_V1(base36_cast_from_text);
Datum
base36_cast_from_text(PG_FUNCTION_ARGS)
{
text *txt = PG_GETARG_TEXT_P(0);
char *str = DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(txt)));
PG_RETURN_INT64(base36_from_str(str));
}
PG_FUNCTION_INFO_V1(base36_cast_to_text);
Datum
base36_cast_to_text(PG_FUNCTION_ARGS)
{
base36 c = PG_GETARG_INT64(0);
text *out = (text *)DirectFunctionCall1(textin, PointerGetDatum(base36_to_str(c)));
PG_RETURN_TEXT_P(out);
}

19
src/base36.h Normal file
View File

@@ -0,0 +1,19 @@
#ifndef BASE36_H
#define BASE36_H
#include "postgres.h"
#include "utils/builtins.h"
#include "libpq/pqformat.h"
typedef long long int base36;
Datum base36_in(PG_FUNCTION_ARGS);
Datum base36_out(PG_FUNCTION_ARGS);
Datum base36_recv(PG_FUNCTION_ARGS);
Datum base36_send(PG_FUNCTION_ARGS);
Datum base36_cast_to_text(PG_FUNCTION_ARGS);
Datum base36_cast_from_text(PG_FUNCTION_ARGS);
Datum base36_cast_to_bigint(PG_FUNCTION_ARGS);
Datum base36_cast_from_bigint(PG_FUNCTION_ARGS);
#endif // BASE36_H