Skip to content
Browse files

Use new MapReduce component everywhere

This removes the need for CouchJS, and therefore to link against
Spidermonkey, to use plain old couch views and for rereducing
values when doing view merging.

Change-Id: I9d1f4d749d490ac3fd33db4d74b9e5e69b6908c1
Reviewed-on: http://review.couchbase.org/14112
Reviewed-by: Damien Katz <damien@couchbase.com>
Tested-by: Damien Katz <damien@couchbase.com>
  • Loading branch information...
1 parent 6f4d485 commit eaa3f3be6e5e3c580ccda3673121019f4f9cc6ae @fdmanana fdmanana committed with Damien Katz Mar 20, 2012
Showing with 412 additions and 2,612 deletions.
  1. +4 −103 configure.ac
  2. +4 −0 src/couch_set_view/src/couch_set_view_mapreduce.erl
  3. +2 −0 src/couchdb/Makefile.am
  4. +2 −2 src/couchdb/couch_db.hrl
  5. +2 −1 src/couchdb/couch_index_merger.erl
  6. +11 −5 src/couchdb/couch_view.erl
  7. +3 −1 src/couchdb/couch_view_compactor.erl
  8. +7 −12 src/couchdb/couch_view_group.erl
  9. +301 −0 src/couchdb/couch_view_mapreduce.erl
  10. +27 −16 src/couchdb/couch_view_merger.erl
  11. +47 −31 src/couchdb/couch_view_updater.erl
  12. +1 −53 src/couchdb/priv/Makefile.am
  13. +0 −694 src/couchdb/priv/couch_js/http.c
  14. +0 −18 src/couchdb/priv/couch_js/http.h
  15. +0 −357 src/couchdb/priv/couch_js/main.c
  16. +0 −286 src/couchdb/priv/couch_js/utf8.c
  17. +0 −19 src/couchdb/priv/couch_js/utf8.h
  18. +0 −62 src/couchdb/priv/couch_js/win32/couchjs.vcxproj.tpl.in
  19. +0 −2 src/couchdb/priv/couch_js/win32/msbuild.bat.tpl.in
  20. +0 −20 src/couchdb/priv/spawnkillable/couchspawnkillable.sh
  21. +0 −145 src/couchdb/priv/spawnkillable/couchspawnkillable_win.c
  22. +0 −26 test/etap/170-os-daemons.es
  23. +0 −115 test/etap/170-os-daemons.t
  24. +0 −85 test/etap/171-os-daemons-config.es
  25. +0 −75 test/etap/171-os-daemons-config.t
  26. +0 −17 test/etap/172-os-daemon-errors.1.sh
  27. +0 −15 test/etap/172-os-daemon-errors.2.sh
  28. +0 −15 test/etap/172-os-daemon-errors.3.sh
  29. +0 −15 test/etap/172-os-daemon-errors.4.sh
  30. +0 −128 test/etap/172-os-daemon-errors.t
  31. +0 −117 test/etap/173-os-daemon-cfg-register.t
  32. +0 −165 test/etap/210-os-proc-pool.t
  33. +0 −11 test/etap/Makefile.am
  34. +1 −1 test/etap/run.tpl
View
107 configure.ac
@@ -140,31 +140,6 @@ AC_SUBST(ERL_INTERFACE_DIR_INCLUDE)
ERL_INTERFACE_DIR_LIB="-L`${ERL} -eval 'io:put_chars(code:lib_dir(erl_interface) ++ "/lib"), timer:sleep(10), erlang:halt().' -noshell -noinput`"
AC_SUBST(ERL_INTERFACE_DIR_LIB)
-AC_ARG_WITH([js-include], [AC_HELP_STRING([--with-js-include=PATH],
- [set PATH to the SpiderMonkey include directory])], [
- JS_INCLUDE="$withval"
- JS_FLAGS="-I$JS_INCLUDE"
-], [
- JS_FLAGS="-I/usr/include"
- JS_FLAGS="$JS_FLAGS -I/usr/include/js"
- JS_FLAGS="$JS_FLAGS -I/usr/include/mozjs"
- JS_FLAGS="$JS_FLAGS -I/usr/local/include"
- JS_FLAGS="$JS_FLAGS -I/opt/local/include"
- JS_FLAGS="$JS_FLAGS -I/usr/local/include/js"
- JS_FLAGS="$JS_FLAGS -I/opt/local/include/js"
-])
-AC_SUBST(JS_INCLUDE)
-
-AC_ARG_WITH([js-lib], [AC_HELP_STRING([--with-js-lib=PATH],
- [set PATH to the SpiderMonkey library directory])],
- [
- JS_LIB_DIR=$withval
- JS_LIB_FLAGS="-L$withval"
-], [
- JS_LIB_DIR=
-])
-AC_SUBST(JS_LIB_DIR)
-
AC_ARG_VAR([ERLC_FLAGS], [general flags to prepend to ERLC_FLAGS])
AC_ARG_VAR([FLAGS], [general flags to prepend to LDFLAGS and CPPFLAGS])
@@ -173,12 +148,12 @@ if test "$ac_test_ERLCFLAGS" != set; then
ERLC_FLAGS=+debug_info
fi
-LIB_FLAGS="$JS_LIB_FLAGS -L/usr/local/lib -L/opt/local/lib"
+LIB_FLAGS="-L/usr/local/lib -L/opt/local/lib"
LIBS="$LIB_FLAGS $LIBS"
case "$(uname -s)" in
CYGWIN*|MINGW*)
- FLAGS="$LIB_FLAGS $ERLANG_FLAGS $JS_FLAGS -DXP_WIN $FLAGS"
+ FLAGS="$LIB_FLAGS $ERLANG_FLAGS -DXP_WIN $FLAGS"
CPPFLAGS="$FLAGS $CPPFLAGS"
LDFLAGS="$FLAGS $LDFLAGS"
IS_WINDOWS="TRUE"
@@ -189,7 +164,7 @@ case "$(uname -s)" in
;;
*)
# XP_UNIX required for jsapi.h and has been tested to work on Linux and Darwin.
- FLAGS="$LIB_FLAGS $ERLANG_FLAGS $JS_FLAGS -DXP_UNIX $FLAGS"
+ FLAGS="$LIB_FLAGS $ERLANG_FLAGS -DXP_UNIX $FLAGS"
CPPFLAGS="$FLAGS $CPPFLAGS"
# manually linking libm is requred for FreeBSD 7.0
LDFLAGS="$FLAGS -lm $LDFLAGS"
@@ -198,25 +173,6 @@ esac
AM_CONDITIONAL([WINDOWS], [test x$IS_WINDOWS = xTRUE])
-AC_ARG_ENABLE([spidermonkey],
- [AS_HELP_STRING([--disable-spidermonkey],
- [Allow to build without spidermonkey (things may not work as you expect) @<:@default=off@:>@])],
- [ac_enable_spidermonkey="$enableval"],
- [ac_enable_spidermonkey="yes"])
-
-AM_CONDITIONAL([BUILD_COUCHJS], [ test "x${ac_enable_spidermonkey}" = "xyes" ])
-
-AS_IF([ test "x${ac_enable_spidermonkey}" = "xyes" ], [
- AC_CHECK_LIB([mozjs], [JS_NewContext], [JS_LIB_BASE=mozjs], [
- AC_CHECK_LIB([js], [JS_NewContext], [JS_LIB_BASE=js], [
- AC_CHECK_LIB([js3250], [JS_NewContext], [JS_LIB_BASE=js3250], [
- AC_CHECK_LIB([js32], [JS_NewContext], [JS_LIB_BASE=js32], [
- AC_MSG_ERROR([Could not find the js library.
-
-Is the Mozilla SpiderMonkey library installed?])])])])])
-
-AC_SUBST(JS_LIB_BASE)
-
AC_ARG_WITH([v8-lib], [AC_HELP_STRING([--with-v8-lib=PATH],
[set PATH to the V8 library directory])],
[
@@ -248,19 +204,6 @@ AC_CHECK_HEADER([v8.h], [], [AC_MSG_ERROR([Could not find the V8 JavaScript engi
# [AC_MSG_ERROR([Could not find the V8 JavaScript engine library.])])
if test x${IS_WINDOWS} = xTRUE; then
- if test -f "$JS_LIB_DIR/$JS_LIB_BASE.dll"; then
- # seamonkey 1.7- build layout on Windows
- JS_LIB_BINARY="$JS_LIB_DIR/$JS_LIB_BASE.dll"
- else
- # seamonkey 1.8+ build layout on Windows
- if test -f "$JS_LIB_DIR/../bin/$JS_LIB_BASE.dll"; then
- JS_LIB_BINARY="$JS_LIB_DIR/../bin/$JS_LIB_BASE.dll"
- else
- AC_MSG_ERROR([Could not find $JS_LIB_BASE.dll.])
- fi
- fi
- AC_SUBST(JS_LIB_BINARY)
-
# On windows we need to know the path to the openssl binaries.
AC_ARG_WITH([openssl-bin-dir], [AC_HELP_STRING([--with-openssl-bin-dir=PATH],
[path to the open ssl binaries for distribution on Windows])], [
@@ -317,37 +260,6 @@ if test x${IS_WINDOWS} = xTRUE; then
fi
fi
-JSLIB=-l$JS_LIB_BASE
-
-AC_CHECK_HEADER([jsapi.h], [], [
- AC_CHECK_HEADER([js/jsapi.h],
- [
- CPPFLAGS="$CPPFLAGS -I$JS_INCLUDE/js"
- ],
- [
- AC_MSG_ERROR([Could not find the jsapi header.
-
-Are the Mozilla SpiderMonkey headers installed?])
- ])])
-
-AC_SUBST(JSLIB)
-
-AC_LANG_PUSH(C)
-OLD_CFLAGS="$CFLAGS"
-CFLAGS="-Werror-implicit-function-declaration"
-AC_COMPILE_IFELSE(
- [AC_LANG_PROGRAM(
- [[#include <jsapi.h>]],
- [[JS_SetOperationCallback(0, 0);]]
- )],
- AC_DEFINE([USE_JS_SETOPCB], [], [Use new JS_SetOperationCallback])
-)
-CFLAGS="$OLD_CFLAGS"
-AC_LANG_POP(C)
-
-# END allow building without spidermonkey
-])
-
AC_ARG_WITH([win32-icu-binaries], [AC_HELP_STRING([--with-win32-icu-binaries=PATH],
[set PATH to the Win32 native ICU binaries directory])], [
ICU_CONFIG="" # supposed to be a command to query options...
@@ -507,7 +419,7 @@ AC_ARG_VAR([HELP2MAN_EXECUTABLE], [path to the `help2man' program])
if test -n "$HELP2MAN_EXECUTABLE"; then
help2man_enabled=true
else
- if test -f "$srcdir/bin/couchdb.1" -a -f "$srcdir/bin/couchjs.1"; then
+ if test -f "$srcdir/bin/couchdb.1"; then
help2man_enabled=true
else
help2man_enabled=false
@@ -601,8 +513,6 @@ AC_CONFIG_FILES([test/python/set_view/Makefile])
AC_CONFIG_FILES([utils/Makefile])
AC_CONFIG_FILES([var/Makefile])
if test x${IS_WINDOWS} = xTRUE; then
- AC_CONFIG_FILES([src/couchdb/priv/couch_js/win32/msbuild.bat.tpl])
- AC_CONFIG_FILES([src/couchdb/priv/couch_js/win32/couchjs.vcxproj.tpl])
AC_CONFIG_FILES([src/couchdb/priv/icu_driver/win32/msbuild.bat.tpl])
AC_CONFIG_FILES([src/couchdb/priv/icu_driver/win32/couch_icu_driver.vcxproj.tpl])
AC_CONFIG_FILES([src/couchdb/priv/couch_ejson_compare/win32/msbuild.bat.tpl])
@@ -631,15 +541,6 @@ if test x${IS_WINDOWS} = xTRUE; then
# probably would chmod +x if we weren't on windows...
fi
-AS_IF([ test "x${ac_enable_spidermonkey}" != "xyes" ], [
-echo
-echo You have chosen to build CouchDB _without_ spidermonkey. This means
-echo that a lot of things won't work for you, and you may encounter tons
-echo of trouble. You have been warned, so you're on your own if you choose
-echo to continue building.
-echo
-])
-
echo
echo "You have configured Apache CouchDB, time to relax."
echo
View
4 src/couch_set_view/src/couch_set_view_mapreduce.erl
@@ -18,6 +18,7 @@
-export([start_map_context/1, start_reduce_context/1]).
-export([end_map_context/0, end_reduce_context/1]).
-export([map/1, reduce/2, reduce/3, rereduce/2, rereduce/3]).
+-export([builtin_reduce/3]).
start_map_context(#set_view_group{views = Views}) ->
@@ -213,6 +214,9 @@ group_reductions_results(List) ->
end.
+builtin_reduce(ReduceType, FunSrcs, Values) ->
+ builtin_reduce(ReduceType, FunSrcs, Values, []).
+
builtin_reduce(_Re, [], _KVs, Acc) ->
{ok, lists:reverse(Acc)};
builtin_reduce(Re, [<<"_sum", _/binary>> | BuiltinReds], KVs, Acc) ->
View
2 src/couchdb/Makefile.am
@@ -91,6 +91,7 @@ source_files = \
couch_view_compactor.erl \
couch_view_updater.erl \
couch_view_group.erl \
+ couch_view_mapreduce.erl \
couch_db_updater.erl \
couch_work_queue.erl \
json_stream_parse.erl \
@@ -170,6 +171,7 @@ compiled_files = \
couch_view_compactor.beam \
couch_view_updater.beam \
couch_view_group.beam \
+ couch_view_mapreduce.beam \
couch_db_updater.beam \
couch_work_queue.beam \
json_stream_parse.beam \
View
4 src/couchdb/couch_db.hrl
@@ -231,7 +231,6 @@
id_btree=nil,
current_seq=0,
purge_seq=0,
- query_server=nil,
waiting_delayed_commit=nil
}).
@@ -243,7 +242,8 @@
def,
btree=nil,
reduce_funs=[],
- options=[]
+ options=[],
+ ref
}).
-record(index_header,
View
3 src/couchdb/couch_index_merger.erl
@@ -296,7 +296,8 @@ get_ddoc(#httpdb{url = BaseUrl, headers = Headers} = HttpDb, Id) ->
case ibrowse:send_req(
Url, Headers, get, [], HttpDb#httpdb.ibrowse_options) of
{ok, "200", _RespHeaders, Body} ->
- {ok, couch_doc:from_json_obj(?JSON_DECODE(Body))};
+ Doc = couch_doc:from_json_obj(?JSON_DECODE(Body)),
+ {ok, couch_doc:with_ejson_body(Doc)};
{ok, _Code, _RespHeaders, Body} ->
{Props} = ?JSON_DECODE(Body),
case {get_value(<<"error">>, Props), get_value(<<"reason">>, Props)} of
View
16 src/couchdb/couch_view.erl
@@ -192,24 +192,30 @@ fold_reduce({temp_reduce, #view{btree=Bt}}, Fun, Acc, Options) ->
end,
couch_btree:fold_reduce(Bt, WrapperFun, Acc, Options);
-fold_reduce({reduce, NthRed, Lang, #view{btree=Bt, reduce_funs=RedFuns}}, Fun, Acc, Options) ->
+fold_reduce({reduce, NthRed, _Lang, View}, Fun, Acc, Options) ->
+ #view{btree=Bt, reduce_funs=RedFuns} = View,
PreResultPadding = lists:duplicate(NthRed - 1, []),
PostResultPadding = lists:duplicate(length(RedFuns) - NthRed, []),
- {_Name, FunSrc} = lists:nth(NthRed,RedFuns),
+ couch_view_mapreduce:start_reduce_context(View),
ReduceFun =
fun(reduce, KVs) ->
- {ok, Reduced} = couch_query_servers:reduce(Lang, [FunSrc], detuple_kvs(expand_dups(KVs, []),[])),
+ KVs2 = expand_dups(KVs, []),
+ {ok, Reduced} = couch_view_mapreduce:reduce(View, NthRed, KVs2),
{0, PreResultPadding ++ Reduced ++ PostResultPadding};
(rereduce, Reds) ->
UserReds = [[lists:nth(NthRed, UserRedsList)] || {_, UserRedsList} <- Reds],
- {ok, Reduced} = couch_query_servers:rereduce(Lang, [FunSrc], UserReds),
+ {ok, Reduced} = couch_view_mapreduce:rereduce(View, NthRed, UserReds),
{0, PreResultPadding ++ Reduced ++ PostResultPadding}
end,
WrapperFun = fun({GroupedKey, _}, PartialReds, Acc0) ->
{_, Reds} = couch_btree:final_reduce(ReduceFun, PartialReds),
Fun(GroupedKey, lists:nth(NthRed, Reds), Acc0)
end,
- couch_btree:fold_reduce(Bt, WrapperFun, Acc, Options).
+ try
+ couch_btree:fold_reduce(Bt, WrapperFun, Acc, Options)
+ after
+ couch_view_mapreduce:end_reduce_context(View)
+ end.
get_key_pos(_Key, [], _N) ->
0;
View
4 src/couchdb/couch_view_compactor.erl
@@ -131,9 +131,11 @@ compact_view(Fd, View, #view{btree=ViewBtree}=EmptyView, Acc0) ->
{Item, update_task(Acc, 1)}
end,
- % Copy each view btree.
+ couch_view_mapreduce:start_reduce_context(View),
{ok, NewBtreeRoot, Acc2} = couch_btree_copy:copy(View#view.btree, Fd,
[{before_kv_write, {BeforeKVWriteFun, Acc0}}]),
+ couch_view_mapreduce:end_reduce_context(View),
+
ViewBtree2 = ViewBtree#btree{root = NewBtreeRoot},
{EmptyView#view{btree = ViewBtree2}, Acc2}.
View
19 src/couchdb/couch_view_group.erl
@@ -622,8 +622,7 @@ design_doc_to_view_group(#doc{id=Id,body={Fields}}) ->
reset_group(#group{views=Views}=Group) ->
Views2 = [View#view{btree=nil} || View <- Views],
- Group#group{fd=nil,query_server=nil,current_seq=0,
- id_btree=nil,views=Views2}.
+ Group#group{fd=nil, current_seq=0, id_btree=nil, views=Views2}.
reset_file(Db, Fd, DbName, #group{sig=Sig,name=Name} = Group) ->
?LOG_DEBUG("Resetting group index \"~s\" in db ~s", [Name, DbName]),
@@ -638,8 +637,8 @@ init_group(Db, Fd, #group{views=Views}=Group, nil) ->
init_group(Db, Fd, Group,
#index_header{seq=0, purge_seq=couch_db:get_purge_seq(Db),
id_btree_state=nil, view_states=[{nil, 0, 0} || _ <- Views]});
-init_group(_Db, Fd, #group{def_lang=Lang,views=Views}=
- Group, IndexHeader) ->
+init_group(_Db, Fd, #group{views=Views0} = Group, IndexHeader) ->
+ Views = [V#view{ref = make_ref()} || V <- Views0],
#index_header{seq=Seq, purge_seq=PurgeSeq,
id_btree_state=IdBtreeState, view_states=ViewStates} = IndexHeader,
StateUpdate = fun
@@ -650,20 +649,16 @@ init_group(_Db, Fd, #group{def_lang=Lang,views=Views}=
{ok, IdBtree} = couch_btree:open(
IdBtreeState, Fd, []),
Views2 = lists:zipwith(
- fun({BTState, USeq, PSeq}, #view{reduce_funs=RedFuns,options=Options}=View) ->
- FunSrcs = [FunSrc || {_Name, FunSrc} <- RedFuns],
+ fun({BTState, USeq, PSeq}, #view{options=Options} = View) ->
ReduceFun =
fun(reduce, KVs) ->
KVs2 = couch_view:expand_dups(KVs,[]),
- KVs3 = couch_view:detuple_kvs(KVs2,[]),
- {ok, Reduced} = couch_query_servers:reduce(Lang, FunSrcs,
- KVs3),
- {length(KVs3), Reduced};
+ {ok, Reduced} = couch_view_mapreduce:reduce(View, KVs2),
+ {length(KVs2), Reduced};
(rereduce, Reds) ->
Count = lists:sum([Count0 || {Count0, _} <- Reds]),
UserReds = [UserRedsList || {_, UserRedsList} <- Reds],
- {ok, Reduced} = couch_query_servers:rereduce(Lang, FunSrcs,
- UserReds),
+ {ok, Reduced} = couch_view_mapreduce:rereduce(View, UserReds),
{Count, Reduced}
end,
View
301 src/couchdb/couch_view_mapreduce.erl
@@ -0,0 +1,301 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_view_mapreduce).
+
+-include("couch_db.hrl").
+
+-export([start_map_context/1, start_reduce_context/1]).
+-export([end_map_context/0, end_reduce_context/1]).
+-export([map/1, reduce/2, reduce/3, rereduce/2, rereduce/3]).
+
+
+start_map_context(#group{views = Views}) ->
+ {ok, Ctx} = mapreduce:start_map_context([View#view.def || View <- Views]),
+ erlang:put(map_context, Ctx),
+ ok.
+
+
+end_map_context() ->
+ erlang:erase(map_context),
+ ok.
+
+
+start_reduce_context(#group{views = Views}) ->
+ lists:foreach(fun start_reduce_context/1, Views);
+
+start_reduce_context(#view{ref = Ref, reduce_funs = RedFuns}) ->
+ FunSrcs = lists:foldr(
+ fun({_Name, <<"_", _/binary>>}, Acc) ->
+ Acc;
+ ({_Name, Src}, Acc) ->
+ [Src | Acc]
+ end,
+ [], RedFuns),
+ case FunSrcs of
+ [] ->
+ ok;
+ _ ->
+ {ok, Ctx} = mapreduce:start_reduce_context(FunSrcs),
+ erlang:put({reduce_context, Ref}, Ctx),
+ ok
+ end.
+
+
+end_reduce_context(#group{views = Views}) ->
+ lists:foreach(fun end_reduce_context/1, Views);
+
+end_reduce_context(#view{ref = Ref}) ->
+ erlang:erase({reduce_context, Ref}),
+ ok.
+
+
+map(Doc) ->
+ Ctx = erlang:get(map_context),
+ DocBin = couch_doc:to_raw_json_binary(Doc),
+ case mapreduce:map_doc(Ctx, DocBin) of
+ {ok, Results} ->
+ {ok, [
+ [{?JSON_DECODE(K), ?JSON_DECODE(V)} || {K, V} <- FunResult]
+ || FunResult <- Results
+ ]};
+ Error ->
+ throw(Error)
+ end.
+
+
+reduce(#view{reduce_funs = []}, _KVs) ->
+ {ok, []};
+reduce(#view{ref = Ref, reduce_funs = RedFuns}, KVs0) ->
+ RedFunSources = [FunSource || {_Name, FunSource} <- RedFuns],
+ {NativeFuns, JsFuns} = lists:partition(
+ fun(<<"_", _/binary>>) -> true; (_) -> false end,
+ RedFunSources),
+ case JsFuns of
+ [] ->
+ builtin_reduce(reduce, NativeFuns, KVs0, []);
+ _ ->
+ KVs = encode_kvs(KVs0, []),
+ {ok, NativeResults} = builtin_reduce(reduce, NativeFuns, KVs, []),
+ Ctx = erlang:get({reduce_context, Ref}),
+ case mapreduce:reduce(Ctx, KVs) of
+ {ok, JsResults0} ->
+ JsResults = [?JSON_DECODE(R) || R <- JsResults0],
+ recombine_reduce_results(RedFunSources, JsResults, NativeResults, []);
+ Error ->
+ throw(Error)
+ end
+ end.
+
+
+reduce(#view{reduce_funs = []}, _NthRed, _KVs) ->
+ {ok, []};
+reduce(#view{ref = Ref, reduce_funs = RedFuns}, NthRed, KVs0) ->
+ {Before, [{_Name, FunSrc} | _]} = lists:split(NthRed - 1, RedFuns),
+ case FunSrc of
+ <<"_", _/binary>> ->
+ builtin_reduce(reduce, [FunSrc], KVs0, []);
+ _ ->
+ KVs = encode_kvs(KVs0, []),
+ Ctx = erlang:get({reduce_context, Ref}),
+ NthRed2 = lists:foldl(
+ fun(<<"_", _/binary>>, Acc) ->
+ Acc - 1;
+ (_, Acc) ->
+ Acc
+ end,
+ NthRed,
+ Before),
+ case mapreduce:reduce(Ctx, NthRed2, KVs) of
+ {ok, ReduceValue} ->
+ {ok, [?JSON_DECODE(ReduceValue)]};
+ Error ->
+ throw(Error)
+ end
+ end.
+
+
+rereduce(#view{reduce_funs = []}, _ReducedValues) ->
+ {ok, []};
+rereduce(#view{ref = Ref, reduce_funs = RedFuns}, ReducedValues) ->
+ Grouped = group_reductions_results(ReducedValues),
+ Ctx = erlang:get({reduce_context, Ref}),
+ Results = lists:zipwith(
+ fun({native, FunSrc}, Values) ->
+ {ok, [Result]} = builtin_reduce(rereduce, [FunSrc], [{[], V} || V <- Values], []),
+ Result;
+ (Idx, Values) ->
+ case mapreduce:rereduce(Ctx, Idx, [?JSON_ENCODE(V) || V <- Values]) of
+ {ok, Reduction} ->
+ ?JSON_DECODE(Reduction);
+ Error ->
+ throw(Error)
+ end
+ end, reduce_fun_indexes(RedFuns), Grouped),
+ {ok, Results}.
+
+
+rereduce(#view{reduce_funs = []}, _NthRed, _ReducedValues) ->
+ {ok, []};
+rereduce(#view{ref = Ref, reduce_funs = RedFuns}, NthRed, ReducedValues) ->
+ {Before, [{_Name, FunSrc} | _]} = lists:split(NthRed - 1, RedFuns),
+ [Values] = group_reductions_results(ReducedValues),
+ case FunSrc of
+ <<"_", _/binary>> ->
+ builtin_reduce(rereduce, [FunSrc], [{[], V} || V <- Values], []);
+ _ ->
+ Ctx = erlang:get({reduce_context, Ref}),
+ NthRed2 = lists:foldl(
+ fun(<<"_", _/binary>>, Acc) ->
+ Acc - 1;
+ (_, Acc) ->
+ Acc
+ end,
+ NthRed,
+ Before),
+ case mapreduce:rereduce(Ctx, NthRed2, [?JSON_ENCODE(V) || V <- Values]) of
+ {ok, ReduceValue} ->
+ {ok, [?JSON_DECODE(ReduceValue)]};
+ Error ->
+ throw(Error)
+ end
+ end.
+
+
+reduce_fun_indexes(RedFuns) ->
+ {L, _} = lists:mapfoldl(
+ fun({_Name, <<"_", _/binary>> = Src}, Idx) ->
+ {{native, Src}, Idx};
+ ({_Name, _JsSrc}, Idx) ->
+ {Idx, Idx + 1}
+ end,
+ 1, RedFuns),
+ lists:reverse(L).
+
+
+recombine_reduce_results([], [], [], Acc) ->
+ {ok, lists:reverse(Acc)};
+recombine_reduce_results([<<"_", _/binary>> | RedSrcs], JsResults, [BRes | BuiltinResults], Acc) ->
+ recombine_reduce_results(RedSrcs, JsResults, BuiltinResults, [BRes | Acc]);
+recombine_reduce_results([_JsFun | RedSrcs], [JsR | JsResults], BuiltinResults, Acc) ->
+ recombine_reduce_results(RedSrcs, JsResults, BuiltinResults, [JsR | Acc]).
+
+
+group_reductions_results([]) ->
+ [];
+group_reductions_results(List) ->
+ {Heads, Tails} = lists:foldl(
+ fun([H | T], {HAcc, TAcc}) ->
+ {[H | HAcc], [T | TAcc]}
+ end,
+ {[], []}, List),
+ case Tails of
+ [[] | _] -> % no tails left
+ [Heads];
+ _ ->
+ [Heads | group_reductions_results(Tails)]
+ end.
+
+
+builtin_reduce(_Re, [], _KVs, Acc) ->
+ {ok, lists:reverse(Acc)};
+builtin_reduce(Re, [<<"_sum", _/binary>> | BuiltinReds], KVs, Acc) ->
+ case Re of
+ reduce ->
+ KVs2 = contract_kvs(KVs, []);
+ rereduce ->
+ KVs2 = KVs
+ end,
+ Sum = builtin_sum_rows(KVs2),
+ builtin_reduce(Re, BuiltinReds, KVs, [Sum | Acc]);
+builtin_reduce(reduce, [<<"_count", _/binary>> | BuiltinReds], KVs, Acc) ->
+ Count = length(KVs),
+ builtin_reduce(reduce, BuiltinReds, KVs, [Count | Acc]);
+builtin_reduce(rereduce, [<<"_count", _/binary>> | BuiltinReds], KVs, Acc) ->
+ Count = builtin_sum_rows(KVs),
+ builtin_reduce(rereduce, BuiltinReds, KVs, [Count | Acc]);
+builtin_reduce(Re, [<<"_stats", _/binary>> | BuiltinReds], KVs, Acc) ->
+ case Re of
+ reduce ->
+ KVs2 = contract_kvs(KVs, []);
+ rereduce ->
+ KVs2 = KVs
+ end,
+ Stats = builtin_stats(Re, KVs2),
+ builtin_reduce(Re, BuiltinReds, KVs, [Stats | Acc]);
+builtin_reduce(_Re, [InvalidBuiltin | _BuiltinReds], _KVs, _Acc) ->
+ throw({invalid_builtin_reduce_function, InvalidBuiltin}).
+
+
+builtin_sum_rows(KVs) ->
+ lists:foldl(fun
+ ({_Key, Value}, Acc) when is_number(Value), is_number(Acc) ->
+ Acc + Value;
+ ({_Key, Value}, Acc) when is_list(Value), is_list(Acc) ->
+ sum_terms(Acc, Value);
+ ({_Key, Value}, Acc) when is_number(Value), is_list(Acc) ->
+ sum_terms(Acc, [Value]);
+ ({_Key, Value}, Acc) when is_list(Value), is_number(Acc) ->
+ sum_terms([Acc], Value);
+ (_Else, _Acc) ->
+ throw({invalid_value, <<"builtin _sum function requires map values to be numbers or lists of numbers">>})
+ end, 0, KVs).
+
+sum_terms([], []) ->
+ [];
+sum_terms([_ | _] = Xs, []) ->
+ Xs;
+sum_terms([], [_ | _] = Ys) ->
+ Ys;
+sum_terms([X | Xs], [Y | Ys]) when is_number(X), is_number(Y) ->
+ [X + Y | sum_terms(Xs, Ys)];
+sum_terms(_, _) ->
+ throw({invalid_value, <<"builtin _sum function requires map values to be numbers or lists of numbers">>}).
+
+builtin_stats(reduce, []) ->
+ {[]};
+builtin_stats(reduce, [{_, First} | Rest]) when is_number(First) ->
+ Stats = lists:foldl(fun({_K, V}, {S, C , Mi, Ma, Sq}) when is_number(V) ->
+ {S + V, C + 1, erlang:min(Mi, V), erlang:max(Ma, V), Sq + (V * V)};
+ (_, _) ->
+ throw({invalid_value,
+ <<"builtin _stats function requires map values to be numbers">>})
+ end, {First, 1, First, First, First * First}, Rest),
+ {Sum, Cnt, Min, Max, Sqr} = Stats,
+ {[{sum, Sum}, {count, Cnt}, {min, Min}, {max, Max}, {sumsqr, Sqr}]};
+builtin_stats(reduce, KVs) when is_list(KVs) ->
+ Msg = <<"builtin _stats function requires map values to be numbers">>,
+ throw({invalid_value, Msg});
+
+builtin_stats(rereduce, [{_, First} | Rest]) ->
+ {[{sum, Sum0}, {count, Cnt0}, {min, Min0}, {max, Max0}, {sumsqr, Sqr0}]} = First,
+ Stats = lists:foldl(fun({_K, Red}, {S, C, Mi, Ma, Sq}) ->
+ {[{sum, Sum}, {count, Cnt}, {min, Min}, {max, Max}, {sumsqr, Sqr}]} = Red,
+ {Sum + S, Cnt + C, erlang:min(Min, Mi), erlang:max(Max, Ma), Sqr + Sq}
+ end, {Sum0, Cnt0, Min0, Max0, Sqr0}, Rest),
+ {Sum, Cnt, Min, Max, Sqr} = Stats,
+ {[{sum, Sum}, {count, Cnt}, {min, Min}, {max, Max}, {sumsqr, Sqr}]}.
+
+
+contract_kvs([], Acc) ->
+ lists:reverse(Acc);
+contract_kvs([KV | Rest], Acc) ->
+ {{Key, Id}, Value} = KV,
+ NKV = {[Key, Id], Value},
+ contract_kvs(Rest, [NKV | Acc]).
+
+encode_kvs([], Acc) ->
+ lists:reverse(Acc);
+encode_kvs([KV | Rest], Acc) ->
+ {{Key, Id}, Value} = KV,
+ NKV = {?JSON_ENCODE([Key, Id]), ?JSON_ENCODE(Value)},
+ encode_kvs(Rest, [NKV | Acc]).
View
43 src/couchdb/couch_view_merger.erl
@@ -245,22 +245,22 @@ view_row_obj_map({{Key, error}, Value}, _DebugMode) ->
{[{key, Key}, {error, Value}]};
% set view
-view_row_obj_map({{Key, DocId}, {PartId, Value}}, true) ->
+view_row_obj_map({{Key, DocId}, {PartId, Value}}, true) when is_integer(PartId) ->
{[{id, DocId}, {key, Key}, {partition, PartId}, {node, ?LOCAL}, {value, Value}]};
-view_row_obj_map({{Key, DocId}, {PartId, Node, Value}}, true) ->
+view_row_obj_map({{Key, DocId}, {PartId, Node, Value}}, true) when is_integer(PartId) ->
{[{id, DocId}, {key, Key}, {partition, PartId}, {node, Node}, {value, Value}]};
-view_row_obj_map({{Key, DocId}, {_PartId, Value}}, false) ->
+view_row_obj_map({{Key, DocId}, {PartId, Value}}, false) when is_integer(PartId) ->
{[{id, DocId}, {key, Key}, {value, Value}]};
view_row_obj_map({{Key, DocId}, Value}, _DebugMode) ->
{[{id, DocId}, {key, Key}, {value, Value}]};
% set view
-view_row_obj_map({{Key, DocId}, {PartId, Value}, Doc}, true) ->
+view_row_obj_map({{Key, DocId}, {PartId, Value}, Doc}, true) when is_integer(PartId) ->
{[{id, DocId}, {key, Key}, {partition, PartId}, {node, ?LOCAL}, {value, Value}, Doc]};
-view_row_obj_map({{Key, DocId}, {PartId, Node, Value}, Doc}, true) ->
+view_row_obj_map({{Key, DocId}, {PartId, Node, Value}, Doc}, true) when is_integer(PartId) ->
{[{id, DocId}, {key, Key}, {partition, PartId}, {node, Node}, {value, Value}, Doc]};
-view_row_obj_map({{Key, DocId}, {_PartId, Value}, Doc}, false) ->
+view_row_obj_map({{Key, DocId}, {PartId, Value}, Doc}, false) when is_integer(PartId) ->
{[{id, DocId}, {key, Key}, {value, Value}, Doc]};
view_row_obj_map({{Key, DocId}, Value, Doc}, _DebugMode) ->
@@ -398,8 +398,7 @@ merge_reduce_min_row(Params, MinRow) ->
{row, _} ->
{ok, Col3} = Col2(Row);
_ ->
- Col3 = Col2,
- ok
+ Col3 = Col2
end,
Limit2 = couch_index_merger:dec_counter(Limit)
end,
@@ -439,14 +438,26 @@ group_keys_for_rereduce(Queue, [{K, _} | _] = Acc) ->
end.
-rereduce(Rows, #merge_params{extra = Extra}) ->
- #view_merge{
- rereduce_fun = RedFun,
- rereduce_fun_lang = Lang
- } = Extra,
- Reds = [[Val] || {_Key, Val} <- Rows],
- {ok, [Value]} = couch_query_servers:rereduce(Lang, [RedFun], Reds),
- Value.
+rereduce(Reds, #merge_params{extra = #view_merge{rereduce_fun = <<"_", _/binary>> = FunSrc}}) ->
+ {ok, [Value]} = couch_set_view_mapreduce:builtin_reduce(rereduce, [FunSrc], Reds),
+ Value;
+
+rereduce(Rows, #merge_params{extra = #view_merge{rereduce_fun = FunSrc}}) ->
+ Reds = [?JSON_ENCODE(Val) || {_Key, Val} <- Rows],
+ case get(reduce_context) of
+ undefined ->
+ {ok, Ctx} = mapreduce:start_reduce_context([FunSrc]),
+ erlang:put(reduce_context, Ctx);
+ Ctx ->
+ ok
+ end,
+ case mapreduce:rereduce(Ctx, 1, Reds) of
+ {ok, Value} ->
+ ?JSON_DECODE(Value);
+ Error ->
+ throw(Error)
+ end.
+
get_set_view(GetSetViewFn, SetName, DDocId, ViewName, ViewGroupReq, Partitions) ->
ViewGroupReq1 = ViewGroupReq#set_view_group_req{
View
78 src/couchdb/couch_view_updater.erl
@@ -52,7 +52,12 @@ update(Owner, Group, #db{name = DbName} = Db) ->
[{max_size, ?QUEUE_MAX_SIZE}, {max_items, ?QUEUE_MAX_ITEMS}]),
Self = self(),
spawn_link(fun() ->
- do_maps(add_query_server(Group), MapQueue, WriteQueue)
+ couch_view_mapreduce:start_map_context(Group),
+ try
+ do_maps(Group, MapQueue, WriteQueue, [], 0)
+ after
+ couch_view_mapreduce:end_map_context()
+ end
end),
TotalChanges = couch_db:count_changes_since(Db, Seq),
spawn_link(fun() ->
@@ -72,7 +77,12 @@ update(Owner, Group, #db{name = DbName} = Db) ->
Group
end,
ViewEmptyKVs = [{View, []} || View <- Group2#group.views],
- do_writes(Self, Owner, Group2, WriteQueue, Seq == 0, ViewEmptyKVs, [])
+ couch_view_mapreduce:start_reduce_context(Group2),
+ try
+ do_writes(Self, Owner, Group2, WriteQueue, Seq == 0, ViewEmptyKVs, [])
+ after
+ couch_view_mapreduce:end_reduce_context(Group2)
+ end
end),
% compute on all docs modified since we last computed.
#group{ design_options = DesignOptions } = Group,
@@ -101,16 +111,6 @@ update(Owner, Group, #db{name = DbName} = Db) ->
end.
-add_query_server(#group{query_server = nil} = Group) ->
- {ok, Qs} = couch_query_servers:start_doc_map(
- Group#group.def_lang,
- [View#view.def || View <- Group#group.views],
- Group#group.lib),
- Group#group{query_server = Qs};
-add_query_server(Group) ->
- Group.
-
-
purge_index(#group{fd=Fd, views=Views, id_btree=IdBtree}=Group, Db) ->
{ok, PurgedIdsRevs} = couch_db:get_last_purged(Db),
Ids = [Id || {Id, _Revs} <- PurgedIdsRevs],
@@ -164,23 +164,43 @@ load_doc(Db, DocInfo, MapQueue, DocOpts, IncludeDesign) ->
end
end.
-do_maps(#group{query_server = Qs} = Group, MapQueue, WriteQueue) ->
+do_maps(Group, MapQueue, WriteQueue, AccItems, AccItemsSize) ->
case couch_work_queue:dequeue(MapQueue) of
closed ->
- couch_work_queue:close(WriteQueue),
- couch_query_servers:stop_doc_map(Group#group.query_server);
- {ok, Queue, _QueueSize} ->
- lists:foreach(
- fun({Seq, #doc{id = Id, deleted = true}}) ->
+ case AccItems of
+ [] ->
+ ok;
+ _ ->
+ ok = couch_work_queue:queue(WriteQueue, AccItems)
+ end,
+ couch_work_queue:close(WriteQueue);
+ {ok, Queue, QueueSize} ->
+ Items = lists:foldr(
+ fun({Seq, #doc{id = Id, deleted = true}}, Acc) ->
Item = {Seq, Id, []},
- ok = couch_work_queue:queue(WriteQueue, Item);
- ({Seq, #doc{id = Id, deleted = false} = Doc}) ->
- {ok, Result} = couch_query_servers:map_doc_raw(Qs, Doc),
- Item = {Seq, Id, Result},
- ok = couch_work_queue:queue(WriteQueue, Item)
+ [Item | Acc];
+ ({Seq, #doc{id = Id, deleted = false} = Doc}, Acc) ->
+ try
+ {ok, Result} = couch_view_mapreduce:map(Doc),
+ Item = {Seq, Id, Result},
+ [Item | Acc]
+ catch _:{error, Reason} ->
+ ?LOG_ERROR("View group `~s`, error mapping document `~s`: ~s~n",
+ [Group#group.name, Id, couch_util:to_binary(Reason)]),
+ Acc
+ end
end,
- Queue),
- do_maps(Group, MapQueue, WriteQueue)
+ [], Queue),
+ AccItems2 = AccItems ++ Items,
+ AccItemsSize2 = AccItemsSize + QueueSize,
+ case (AccItemsSize2 >= ?QUEUE_MAX_SIZE) orelse
+ (length(AccItems2) >= ?QUEUE_MAX_ITEMS) of
+ true ->
+ ok = couch_work_queue:queue(WriteQueue, AccItems2),
+ do_maps(Group, MapQueue, WriteQueue, [], 0);
+ false ->
+ do_maps(Group, MapQueue, WriteQueue, AccItems2, AccItemsSize2)
+ end
end.
@@ -191,7 +211,7 @@ do_writes(Parent, Owner, Group, WriteQueue, InitialBuild, ViewEmptyKVs, Acc) ->
Parent, Owner, Group, InitialBuild, ViewEmptyKVs, Acc),
Parent ! {new_group, Group2};
{ok, Queue, _QueueSize} ->
- Acc2 = Acc ++ Queue,
+ Acc2 = Acc ++ lists:flatten(Queue),
case length(Acc2) >= ?MIN_FLUSH_BATCH_SIZE of
true ->
Group2 = flush_writes(Parent, Owner, Group, InitialBuild, ViewEmptyKVs, Acc2),
@@ -208,11 +228,7 @@ flush_writes(Parent, Owner, Group, InitialBuild, ViewEmptyKVs, Queue) ->
{ViewKVs, DocIdViewIdKeys} = lists:foldr(
fun({_Seq, Id, []}, {ViewKVsAcc, DocIdViewIdKeysAcc}) ->
{ViewKVsAcc, [{Id, []} | DocIdViewIdKeysAcc]};
- ({_Seq, Id, RawQueryResults}, {ViewKVsAcc, DocIdViewIdKeysAcc}) ->
- QueryResults = [
- [list_to_tuple(FunResult) || FunResult <- FunRs] || FunRs <-
- couch_query_servers:raw_to_ejson(RawQueryResults)
- ],
+ ({_Seq, Id, QueryResults}, {ViewKVsAcc, DocIdViewIdKeysAcc}) ->
{NewViewKVs, NewViewIdKeys} = view_insert_doc_query_results(
Id, QueryResults, ViewKVsAcc, [], []),
{NewViewKVs, [{Id, NewViewIdKeys} | DocIdViewIdKeysAcc]}
View
54 src/couchdb/priv/Makefile.am
@@ -16,7 +16,6 @@ couchprivlibdir = $(couchlibdir)/priv/lib
erlinterfaceldflags = $(ERL_INTERFACE_DIR_LIB) -lerl_interface -lei
EXTRA_DIST = \
- spawnkillable/couchspawnkillable.sh \
stat_descriptions.cfg.in \
couch_ejson_compare/erl_nif_compat.h
@@ -58,49 +57,20 @@ couch_icu_driver_la_LIBADD = $(ICU_LOCAL_LIBS)
endif
if WINDOWS
-couchjs_msbuild = couch_js/win32/msbuild.bat
-couchjs_vcproj = couch_js/win32/couchjs.vcxproj
-WIN_JS_INCLUDE = couch_js/win32/win_js_include.tmp
-WIN_JS_LIB_DIR = couch_js/win32/win_js_dir.tmp
couchpriv_DATA = stat_descriptions.cfg $(COUCH_ICU_DRIVER)
endif
-COUCHJS_SRCS = \
- couch_js/http.c \
- couch_js/http.h \
- couch_js/main.c \
- couch_js/utf8.c \
- couch_js/utf8.h
-
locallibbin_PROGRAMS =
-if BUILD_COUCHJS
-locallibbin_PROGRAMS += couchjs
-endif
-
-couchjs_SOURCES = $(COUCHJS_SRCS)
-couchjs_CFLAGS = -D_BSD_SOURCE $(CURL_CFLAGS)
-couchjs_LDADD = $(CURL_LIBS) @JSLIB@
-
if !WINDOWS
couchpriv_DATA = stat_descriptions.cfg
endif
-couchpriv_PROGRAMS = couchspawnkillable
+couchpriv_PROGRAMS =
%.cfg: %.cfg.in
cp $< $@
if WINDOWS
-couchspawnkillable_SOURCES = spawnkillable/couchspawnkillable_win.c
-endif
-
-if !WINDOWS
-couchspawnkillable: spawnkillable/couchspawnkillable.sh
- cp $< $@
- chmod +x $@
-endif
-
-if WINDOWS
$(COUCH_ICU_DRIVER) : icu_driver/couch_icu_driver.c $(icu_driver_msbuild) $(icu_driver_vcproj)
$(MKDIR_P) "$(couchprivlibdir)" || true
@@ -149,26 +119,6 @@ $(EJSON_COMPARE_VCPROJ): $(EJSON_COMPARE_VCPROJ).tpl $(WIN_ICU_INCLUDE) $(WIN_IC
-e "s|%ICU_LIB_DIR%|`cat $(WIN_ICU_LIB_DIR)`|" \
< $< > $@
-couchjs$(EXEEXT): $(COUCHJS_SRCS) $(couchjs_msbuild) $(couchjs_vcproj)
- @rm -f couchjs$(EXEEXT)
- (cd couch_js/win32 && ./msbuild.bat)
-
-$(couchjs_msbuild): $(couchjs_msbuild).tpl
- sed -e "s|%msbuild_dir%|$(msbuild_dir)|" \
- -e "s|%msbuild_name%|$(msbuild_name)|" \
- -e "s|^/cygdrive/\([a-zA-Z]\)|\1:|" \
- < $< > $@
-
-$(couchjs_vcproj): $(couchjs_vcproj).tpl $(WIN_JS_INCLUDE) $(WIN_JS_LIB_DIR)
- sed -e "s|%JS_INCLUDE%|`cat $(WIN_JS_INCLUDE)`|" \
- -e "s|%JS_LIB_DIR%|`cat $(WIN_JS_LIB_DIR)`|" \
- < $< > $@
-
-$(WIN_JS_INCLUDE):$(JS_INCLUDE)
- echo $< | sed -e "s|^/cygdrive/\([a-zA-Z]\)|\1:|" > $@
-
-$(WIN_JS_LIB_DIR):$(JS_LIB_DIR)
- echo $< | sed -e "s|^/cygdrive/\([a-zA-Z]\)|\1:|" > $@
endif
# libtool and automake have defeated markh. For each of our executables
@@ -193,8 +143,6 @@ if WINDOWS
$(INSTALL) $(ICU_LOCAL_BIN)/icudt44.dll $(bindir)
$(INSTALL) $(ICU_LOCAL_BIN)/icuin44.dll $(bindir)
$(INSTALL) $(JS_LIB_BINARY) $(bindir)
- $(INSTALL) .libs/couchspawnkillable.exe \
- "$(DESTDIR)$(couchprivdir)/couchspawnkillable.exe"
endif
uninstall-local:
View
694 src/couchdb/priv/couch_js/http.c
@@ -1,694 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-#include "config.h"
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <jsapi.h>
-
-#ifndef HAVE_CURL
-
-// Soft dependency on cURL bindings because they're
-// only used when running the JS tests from the
-// command line which is rare.
-JSObject*
-install_http(JSContext* cx, JSObject* glbl)
-{
- fprintf(stderr, "ERROR: couchjs was not built with cURL support.\n");
- return NULL;
-}
-
-#else
-
-#include <curl/curl.h>
-
-#include "utf8.h"
-
-#ifdef XP_WIN
-// Map some of the string function names to things which exist on Windows
-#define strcasecmp _strcmpi
-#define strncasecmp _strnicmp
-#define snprintf _snprintf
-#endif
-
-typedef struct curl_slist CurlHeaders;
-
-typedef struct {
- int method;
- char* url;
- CurlHeaders* req_headers;
- jsint last_status;
-} HTTPData;
-
-char* METHODS[] = {"GET", "HEAD", "POST", "PUT", "DELETE", "COPY", NULL};
-
-#define GET 0
-#define HEAD 1
-#define POST 2
-#define PUT 3
-#define DELETE 4
-#define COPY 5
-
-static JSBool
-go(JSContext* cx, JSObject* obj, HTTPData* http, char* body, size_t blen);
-
-static JSString*
-str_from_binary(JSContext* cx, char* data, size_t length);
-
-static JSBool
-constructor(JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval)
-{
- HTTPData* http = NULL;
- JSBool ret = JS_FALSE;
-
- http = (HTTPData*) malloc(sizeof(HTTPData));
- if(!http)
- {
- JS_ReportError(cx, "Failed to create CouchHTTP instance.");
- goto error;
- }
-
- http->method = -1;
- http->url = NULL;
- http->req_headers = NULL;
- http->last_status = -1;
-
- if(!JS_SetPrivate(cx, obj, http))
- {
- JS_ReportError(cx, "Failed to set private CouchHTTP data.");
- goto error;
- }
-
- ret = JS_TRUE;
- goto success;
-
-error:
- if(http) free(http);
-
-success:
- return ret;
-}
-
-static void
-destructor(JSContext* cx, JSObject* obj)
-{
- HTTPData* http = (HTTPData*) JS_GetPrivate(cx, obj);
- if(!http)
- {
- fprintf(stderr, "Unable to destroy invalid CouchHTTP instance.\n");
- }
- else
- {
- if(http->url) free(http->url);
- if(http->req_headers) curl_slist_free_all(http->req_headers);
- free(http);
- }
-}
-
-static JSBool
-open(JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval)
-{
- HTTPData* http = (HTTPData*) JS_GetPrivate(cx, obj);
- char* method = NULL;
- char* url = NULL;
- JSBool ret = JS_FALSE;
- int methid;
-
- if(!http)
- {
- JS_ReportError(cx, "Invalid CouchHTTP instance.");
- goto done;
- }
-
- if(argv[0] == JSVAL_VOID)
- {
- JS_ReportError(cx, "You must specify a method.");
- goto done;
- }
-
- method = enc_string(cx, argv[0], NULL);
- if(!method)
- {
- JS_ReportError(cx, "Failed to encode method.");
- goto done;
- }
-
- for(methid = 0; METHODS[methid] != NULL; methid++)
- {
- if(strcasecmp(METHODS[methid], method) == 0) break;
- }
-
- if(methid > COPY)
- {
- JS_ReportError(cx, "Invalid method specified.");
- goto done;
- }
-
- http->method = methid;
-
- if(argv[1] == JSVAL_VOID)
- {
- JS_ReportError(cx, "You must specify a URL.");
- goto done;
- }
-
- if(http->url)
- {
- free(http->url);
- http->url = NULL;
- }
-
- http->url = enc_string(cx, argv[1], NULL);
- if(!http->url)
- {
- JS_ReportError(cx, "Failed to encode URL.");
- goto done;
- }
-
- if(argv[2] != JSVAL_VOID && argv[2] != JSVAL_FALSE)
- {
- JS_ReportError(cx, "Synchronous flag must be false if specified.");
- goto done;
- }
-
- if(http->req_headers)
- {
- curl_slist_free_all(http->req_headers);
- http->req_headers = NULL;
- }
-
- // Disable Expect: 100-continue
- http->req_headers = curl_slist_append(http->req_headers, "Expect:");
-
- ret = JS_TRUE;
-
-done:
- if(method) free(method);
- return ret;
-}
-
-static JSBool
-setheader(JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval)
-{
- HTTPData* http = (HTTPData*) JS_GetPrivate(cx, obj);
- char* keystr = NULL;
- char* valstr = NULL;
- char* hdrbuf = NULL;
- size_t hdrlen = -1;
- JSBool ret = JS_FALSE;
-
- if(!http)
- {
- JS_ReportError(cx, "Invalid CouchHTTP instance.");
- goto done;
- }
-
- if(argv[0] == JSVAL_VOID)
- {
- JS_ReportError(cx, "You must speciy a header name.");
- goto done;
- }
-
- keystr = enc_string(cx, argv[0], NULL);
- if(!keystr)
- {
- JS_ReportError(cx, "Failed to encode header name.");
- goto done;
- }
-
- if(argv[1] == JSVAL_VOID)
- {
- JS_ReportError(cx, "You must specify a header value.");
- goto done;
- }
-
- valstr = enc_string(cx, argv[1], NULL);
- if(!valstr)
- {
- JS_ReportError(cx, "Failed to encode header value.");
- goto done;
- }
-
- hdrlen = strlen(keystr) + strlen(valstr) + 3;
- hdrbuf = (char*) malloc(hdrlen * sizeof(char));
- if(!hdrbuf)
- {
- JS_ReportError(cx, "Failed to allocate header buffer.");
- goto done;
- }
-
- snprintf(hdrbuf, hdrlen, "%s: %s", keystr, valstr);
- http->req_headers = curl_slist_append(http->req_headers, hdrbuf);
-
- ret = JS_TRUE;
-
-done:
- if(keystr) free(keystr);
- if(valstr) free(valstr);
- if(hdrbuf) free(hdrbuf);
-
- return ret;
-}
-
-static JSBool
-sendreq(JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval)
-{
- HTTPData* http = (HTTPData*) JS_GetPrivate(cx, obj);
- char* body = NULL;
- size_t bodylen = 0;
- JSBool ret = JS_FALSE;
-
- if(!http)
- {
- JS_ReportError(cx, "Invalid CouchHTTP instance.");
- goto done;
- }
-
- if(argv[0] != JSVAL_VOID && argv[0] != JS_GetEmptyStringValue(cx))
- {
- body = enc_string(cx, argv[0], &bodylen);
- if(!body)
- {
- JS_ReportError(cx, "Failed to encode body.");
- goto done;
- }
- }
-
- ret = go(cx, obj, http, body, bodylen);
-
-done:
- if(body) free(body);
- return ret;
-}
-
-static JSBool
-status(JSContext* cx, JSObject* obj, jsval idval, jsval* vp)
-{
- HTTPData* http = (HTTPData*) JS_GetPrivate(cx, obj);
-
- if(!http)
- {
- JS_ReportError(cx, "Invalid CouchHTTP instance.");
- return JS_FALSE;
- }
-
- if(INT_FITS_IN_JSVAL(http->last_status))
- {
- *vp = INT_TO_JSVAL(http->last_status);
- return JS_TRUE;
- }
- else
- {
- JS_ReportError(cx, "INTERNAL: Invalid last_status");
- return JS_FALSE;
- }
-}
-
-JSClass CouchHTTPClass = {
- "CouchHTTP",
- JSCLASS_HAS_PRIVATE
- | JSCLASS_CONSTRUCT_PROTOTYPE
- | JSCLASS_HAS_RESERVED_SLOTS(2),
- JS_PropertyStub,
- JS_PropertyStub,
- JS_PropertyStub,
- JS_PropertyStub,
- JS_EnumerateStub,
- JS_ResolveStub,
- JS_ConvertStub,
- destructor,
- JSCLASS_NO_OPTIONAL_MEMBERS
-};
-
-JSPropertySpec CouchHTTPProperties[] = {
- {"status", 0, JSPROP_READONLY, status, NULL},
- {0, 0, 0, 0, 0}
-};
-
-JSFunctionSpec CouchHTTPFunctions[] = {
- {"_open", open, 3, 0, 0},
- {"_setRequestHeader", setheader, 2, 0, 0},
- {"_send", sendreq, 1, 0, 0},
- {0, 0, 0, 0, 0}
-};
-
-JSObject*
-install_http(JSContext* cx, JSObject* glbl)
-{
- JSObject* klass = NULL;
- HTTPData* http = NULL;
-
- klass = JS_InitClass(
- cx,
- glbl,
- NULL,
- &CouchHTTPClass,
- constructor,
- 0,
- CouchHTTPProperties,
- CouchHTTPFunctions,
- NULL,
- NULL
- );
-
- if(!klass)
- {
- fprintf(stderr, "Failed to initialize CouchHTTP class.\n");
- return NULL;
- }
-
- return klass;
-}
-
-
-// Curl Helpers
-
-typedef struct {
- HTTPData* http;
- JSContext* cx;
- JSObject* resp_headers;
- char* sendbuf;
- size_t sendlen;
- size_t sent;
- char* recvbuf;
- size_t recvlen;
- size_t read;
-} CurlState;
-
-/*
- * I really hate doing this but this doesn't have to be
- * uber awesome, it just has to work.
- */
-CURL* HTTP_HANDLE = NULL;
-char ERRBUF[CURL_ERROR_SIZE];
-
-static size_t send_body(void *ptr, size_t size, size_t nmem, void *data);
-static int seek_body(void *ptr, curl_off_t offset, int origin);
-static size_t recv_body(void *ptr, size_t size, size_t nmem, void *data);
-static size_t recv_header(void *ptr, size_t size, size_t nmem, void *data);
-
-static JSBool
-go(JSContext* cx, JSObject* obj, HTTPData* http, char* body, size_t bodylen)
-{
- CurlState state;
- JSString* jsbody;
- JSBool ret = JS_FALSE;
- jsval tmp;
-
- state.cx = cx;
- state.http = http;
-
- state.sendbuf = body;
- state.sendlen = bodylen;
- state.sent = 0;
-
- state.recvbuf = NULL;
- state.recvlen = 0;
- state.read = 0;
-
- if(HTTP_HANDLE == NULL)
- {
- HTTP_HANDLE = curl_easy_init();
- curl_easy_setopt(HTTP_HANDLE, CURLOPT_READFUNCTION, send_body);
- curl_easy_setopt(HTTP_HANDLE, CURLOPT_SEEKFUNCTION,
- (curl_seek_callback) seek_body);
- curl_easy_setopt(HTTP_HANDLE, CURLOPT_HEADERFUNCTION, recv_header);
- curl_easy_setopt(HTTP_HANDLE, CURLOPT_WRITEFUNCTION, recv_body);
- curl_easy_setopt(HTTP_HANDLE, CURLOPT_NOPROGRESS, 1);
- curl_easy_setopt(HTTP_HANDLE, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
- curl_easy_setopt(HTTP_HANDLE, CURLOPT_ERRORBUFFER, ERRBUF);
- curl_easy_setopt(HTTP_HANDLE, CURLOPT_COOKIEFILE, "");
- curl_easy_setopt(HTTP_HANDLE, CURLOPT_USERAGENT,
- "CouchHTTP Client - Relax");
- }
-
- if(!HTTP_HANDLE)
- {
- JS_ReportError(cx, "Failed to initialize cURL handle.");
- goto done;
- }
-
- if(http->method < 0 || http->method > COPY)
- {
- JS_ReportError(cx, "INTERNAL: Unknown method.");
- goto done;
- }
-
- curl_easy_setopt(HTTP_HANDLE, CURLOPT_CUSTOMREQUEST, METHODS[http->method]);
- curl_easy_setopt(HTTP_HANDLE, CURLOPT_NOBODY, 0);
- curl_easy_setopt(HTTP_HANDLE, CURLOPT_FOLLOWLOCATION, 1);
- curl_easy_setopt(HTTP_HANDLE, CURLOPT_UPLOAD, 0);
-
- if(http->method == HEAD)
- {
- curl_easy_setopt(HTTP_HANDLE, CURLOPT_NOBODY, 1);
- curl_easy_setopt(HTTP_HANDLE, CURLOPT_FOLLOWLOCATION, 0);
- }
- else if(http->method == POST || http->method == PUT)
- {
- curl_easy_setopt(HTTP_HANDLE, CURLOPT_UPLOAD, 1);
- curl_easy_setopt(HTTP_HANDLE, CURLOPT_FOLLOWLOCATION, 0);
- }
-
- if(body && bodylen)
- {
- curl_easy_setopt(HTTP_HANDLE, CURLOPT_INFILESIZE, bodylen);
- }
- else
- {
- curl_easy_setopt(HTTP_HANDLE, CURLOPT_INFILESIZE, 0);
- }
-
- //curl_easy_setopt(HTTP_HANDLE, CURLOPT_VERBOSE, 1);
-
- curl_easy_setopt(HTTP_HANDLE, CURLOPT_URL, http->url);
- curl_easy_setopt(HTTP_HANDLE, CURLOPT_HTTPHEADER, http->req_headers);
- curl_easy_setopt(HTTP_HANDLE, CURLOPT_READDATA, &state);
- curl_easy_setopt(HTTP_HANDLE, CURLOPT_SEEKDATA, &state);
- curl_easy_setopt(HTTP_HANDLE, CURLOPT_WRITEHEADER, &state);
- curl_easy_setopt(HTTP_HANDLE, CURLOPT_WRITEDATA, &state);
-
- if(curl_easy_perform(HTTP_HANDLE) != 0)
- {
- JS_ReportError(cx, "Failed to execute HTTP request: %s", ERRBUF);
- goto done;
- }
-
- if(!state.resp_headers)
- {
- JS_ReportError(cx, "Failed to recieve HTTP headers.");
- goto done;
- }
-
- tmp = OBJECT_TO_JSVAL(state.resp_headers);
- if(!JS_DefineProperty(
- cx,
- obj,
- "_headers",
- tmp,
- NULL,
- NULL,
- JSPROP_READONLY
- ))
- {
- JS_ReportError(cx, "INTERNAL: Failed to set response headers.");
- goto done;
- }
-
- if(state.recvbuf) // Is good enough?
- {
- state.recvbuf[state.read] = '\0';
- jsbody = dec_string(cx, state.recvbuf, state.read+1);
- if(!jsbody)
- {
- // If we can't decode the body as UTF-8 we forcefully
- // convert it to a string by just forcing each byte
- // to a jschar.
- jsbody = str_from_binary(cx, state.recvbuf, state.read);
- if(!jsbody) {
- if(!JS_IsExceptionPending(cx)) {
- JS_ReportError(cx, "INTERNAL: Failed to decode body.");
- }
- goto done;
- }
- }
- tmp = STRING_TO_JSVAL(jsbody);
- }
- else
- {
- tmp = JS_GetEmptyStringValue(cx);
- }
-
- if(!JS_DefineProperty(
- cx,
- obj,
- "responseText",
- tmp,
- NULL,
- NULL,
- JSPROP_READONLY
- ))
- {
- JS_ReportError(cx, "INTERNAL: Failed to set responseText.");
- goto done;
- }
-
- ret = JS_TRUE;
-
-done:
- if(state.recvbuf) JS_free(cx, state.recvbuf);
- return ret;
-}
-
-static size_t
-send_body(void *ptr, size_t size, size_t nmem, void *data)
-{
- CurlState* state = (CurlState*) data;
- size_t length = size * nmem;
- size_t towrite = state->sendlen - state->sent;
- if(towrite == 0)
- {
- return 0;
- }
-
- if(length < towrite) towrite = length;
-
- //fprintf(stderr, "%lu %lu %lu %lu\n", state->bodyused, state->bodyread, length, towrite);
-
- memcpy(ptr, state->sendbuf + state->sent, towrite);
- state->sent += towrite;
-
- return towrite;
-}
-
-static int
-seek_body(void* ptr, curl_off_t offset, int origin)
-{
- CurlState* state = (CurlState*) ptr;
- if(origin != SEEK_SET) return -1;
-
- state->sent = (size_t) offset;
- return (int) state->sent;
-}
-
-static size_t
-recv_header(void *ptr, size_t size, size_t nmem, void *data)
-{
- CurlState* state = (CurlState*) data;
- char code[4];
- char* header = (char*) ptr;
- size_t length = size * nmem;
- size_t index = 0;
- JSString* hdr = NULL;
- jsuint hdrlen;
- jsval hdrval;
-
- if(length > 7 && strncasecmp(header, "HTTP/1.", 7) == 0)
- {
- if(length < 12)
- {
- return CURLE_WRITE_ERROR;
- }
-
- memcpy(code, header+9, 3*sizeof(char));
- code[3] = '\0';
- state->http->last_status = atoi(code);
-
- state->resp_headers = JS_NewArrayObject(state->cx, 0, NULL);
- if(!state->resp_headers)
- {
- return CURLE_WRITE_ERROR;
- }
-
- return length;
- }
-
- // We get a notice at the \r\n\r\n after headers.
- if(length <= 2)
- {
- return length;
- }
-
- // Append the new header to our array.
- hdr = dec_string(state->cx, header, length);
- if(!hdr)
- {
- return CURLE_WRITE_ERROR;
- }
-
- if(!JS_GetArrayLength(state->cx, state->resp_headers, &hdrlen))
- {
- return CURLE_WRITE_ERROR;
- }
-
- hdrval = STRING_TO_JSVAL(hdr);
- if(!JS_SetElement(state->cx, state->resp_headers, hdrlen, &hdrval))
- {
- return CURLE_WRITE_ERROR;
- }
-
- return length;
-}
-
-static size_t
-recv_body(void *ptr, size_t size, size_t nmem, void *data)
-{
- CurlState* state = (CurlState*) data;
- size_t length = size * nmem;
- char* tmp = NULL;
-
- if(!state->recvbuf)
- {
- state->recvlen = 4096;
- state->read = 0;
- state->recvbuf = JS_malloc(state->cx, state->recvlen);
- }
-
- if(!state->recvbuf)
- {
- return CURLE_WRITE_ERROR;
- }
-
- // +1 so we can add '\0' back up in the go function.
- while(length+1 > state->recvlen - state->read) state->recvlen *= 2;
- tmp = JS_realloc(state->cx, state->recvbuf, state->recvlen);
- if(!tmp) return CURLE_WRITE_ERROR;
- state->recvbuf = tmp;
-
- memcpy(state->recvbuf + state->read, ptr, length);
- state->read += length;
- return length;
-}
-
-JSString*
-str_from_binary(JSContext* cx, char* data, size_t length)
-{
- jschar* conv = (jschar*) JS_malloc(cx, length * sizeof(jschar));
- JSString* ret = NULL;
- size_t i;
-
- if(!conv) return NULL;
-
- for(i = 0; i < length; i++)
- {
- conv[i] = (jschar) data[i];
- }
-
- ret = JS_NewUCString(cx, conv, length);
- if(!ret) JS_free(cx, conv);
-
- return ret;
-}
-
-#endif /* HAVE_CURL */
View
18 src/couchdb/priv/couch_js/http.h
@@ -1,18 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-#ifndef COUCH_JS_HTTP_H
-#define COUCH_JS_HTTP_H
-
-JSObject* install_http(JSContext* cx, JSObject* global);
-
-#endif
View
357 src/couchdb/priv/couch_js/main.c
@@ -1,357 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <jsapi.h>
-#include "config.h"
-
-#include "utf8.h"
-#include "http.h"
-
-int gExitCode = 0;
-
-#ifdef JS_THREADSAFE
-#define SETUP_REQUEST(cx) \
- JS_SetContextThread(cx); \
- JS_BeginRequest(cx);
-#define FINISH_REQUEST(cx) \
- JS_EndRequest(cx); \
- JS_ClearContextThread(cx);
-#else
-#define SETUP_REQUEST(cx)
-#define FINISH_REQUEST(cx)
-#endif
-
-static JSBool
-evalcx(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
-{
- JSString *str;
- JSObject *sandbox;
- JSContext *subcx;
- const jschar *src;
- size_t srclen;
- JSBool ret = JS_FALSE;
- jsval v;
-
- sandbox = NULL;
- if(!JS_ConvertArguments(cx, argc, argv, "S / o", &str, &sandbox))
- {
- return JS_FALSE;
- }
-
- subcx = JS_NewContext(JS_GetRuntime(cx), 8L * 1024L);
- if(!subcx)
- {
- JS_ReportOutOfMemory(cx);
- return JS_FALSE;
- }
-
- SETUP_REQUEST(subcx);
-
- src = JS_GetStringChars(str);
- srclen = JS_GetStringLength(str);
-
- if(!sandbox)
- {
- sandbox = JS_NewObject(subcx, NULL, NULL, NULL);
- if(!sandbox || !JS_InitStandardClasses(subcx, sandbox)) goto done;
- }
-
- if(srclen == 0)
- {
- *rval = OBJECT_TO_JSVAL(sandbox);
- }
- else
- {
- JS_EvaluateUCScript(subcx, sandbox, src, srclen, NULL, 0, rval);
- }
-
- ret = JS_TRUE;
-
-done:
- FINISH_REQUEST(subcx);
- JS_DestroyContext(subcx);
- return ret;
-}
-
-static JSBool
-gc(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
-{
- JS_GC(cx);
- return JS_TRUE;
-}
-
-static JSBool
-print(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
-{
- uintN i;
- char *bytes;
-
- for(i = 0; i < argc; i++)
- {
- bytes = enc_string(cx, argv[i], NULL);
- if(!bytes) return JS_FALSE;
-
- fprintf(stdout, "%s%s", i ? " " : "", bytes);
- JS_free(cx, bytes);
- }
-
- fputc('\n', stdout);
- fflush(stdout);
- return JS_TRUE;
-}
-
-static JSBool
-quit(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
-{
- JS_ConvertArguments(cx, argc, argv, "/ i", &gExitCode);
- return JS_FALSE;
-}
-
-static char*
-readfp(JSContext* cx, FILE* fp, size_t* buflen)
-{
- char* bytes = NULL;
- char* tmp = NULL;
- size_t used = 0;
- size_t byteslen = 256;
- size_t readlen = 0;
-
- bytes = JS_malloc(cx, byteslen);
- if(bytes == NULL) return NULL;
-
- while((readlen = js_fgets(bytes+used, byteslen-used, stdin)) > 0)
- {
- used += readlen;
-
- if(bytes[used-1] == '\n')
- {
- bytes[used-1] = '\0';
- break;
- }
-
- // Double our buffer and read more.
- byteslen *= 2;
- tmp = JS_realloc(cx, bytes, byteslen);
- if(!tmp)
- {
- JS_free(cx, bytes);
- return NULL;
- }
- bytes = tmp;
- }
-
- *buflen = used;
- return bytes;
-}
-
-static JSBool
-readline(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
- jschar *chars;
- JSString *str;
- char* bytes;
- char* tmp;
- size_t byteslen;
-
- /* GC Occasionally */
- JS_MaybeGC(cx);
-
- bytes = readfp(cx, stdin, &byteslen);
- if(!bytes) return JS_FALSE;
-
- /* Treat the empty string specially */
- if(byteslen == 0)
- {
- *rval = JS_GetEmptyStringValue(cx);
- JS_free(cx, bytes);
- return JS_TRUE;
- }
-
- /* Shrink the buffer to the real size */
- tmp = JS_realloc(cx, bytes, byteslen);
- if(!tmp)
- {
- JS_free(cx, bytes);
- return JS_FALSE;
- }
- bytes = tmp;
-
- str = dec_string(cx, bytes, byteslen);
- JS_free(cx, bytes);
-
- if(!str) return JS_FALSE;
-
- *rval = STRING_TO_JSVAL(str);
-
- return JS_TRUE;
-}
-
-static JSBool
-seal(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
- JSObject *target;
- JSBool deep = JS_FALSE;
-
- if (!JS_ConvertArguments(cx, argc, argv, "o/b", &target, &deep))
- return JS_FALSE;
- if (!target)
- return JS_TRUE;
- return JS_SealObject(cx, target, deep);
-}
-
-static void
-execute_script(JSContext *cx, JSObject *obj, const char *filename) {
- FILE *file;
- JSScript *script;
- jsval result;
-
- if(!filename || strcmp(filename, "-") == 0)
- {
- file = stdin;
- }
- else
- {
- file = fopen(filename, "r");
- if (!file)
- {
- fprintf(stderr, "could not open script file %s\n", filename);
- gExitCode = 1;
- return;
- }
- }
-
- script = JS_CompileFileHandle(cx, obj, filename, file);
- if(script)
- {
- JS_ExecuteScript(cx, obj, script, &result);
- JS_DestroyScript(cx, script);
- }
-}
-
-static void
-printerror(JSContext *cx, const char *mesg, JSErrorReport *report)
-{
- if(!report || !JSREPORT_IS_WARNING(report->flags))
- {
- fprintf(stderr, "%s\n", mesg);
- }
-}
-
-static JSFunctionSpec global_functions[] = {
- {"evalcx", evalcx, 0, 0, 0},
- {"gc", gc, 0, 0, 0},
- {"print", print, 0, 0, 0},
- {"quit", quit, 0, 0, 0},
- {"readline", readline, 0, 0, 0},
- {"seal", seal, 0, 0, 0},
- {0, 0, 0, 0, 0}
-};
-
-static JSClass global_class = {
- "GlobalClass",
- JSCLASS_GLOBAL_FLAGS,
- JS_PropertyStub,
- JS_PropertyStub,
- JS_PropertyStub,
- JS_PropertyStub,
- JS_EnumerateStub,
- JS_ResolveStub,
- JS_ConvertStub,
- JS_FinalizeStub,
- JSCLASS_NO_OPTIONAL_MEMBERS
-};
-
-int
-usage()
-{
- fprintf(stderr, "usage: couchjs [-H] [script_name]\n");
- return 1;
-}
-
-int
-main(int argc, const char * argv[])
-{
- JSRuntime* rt = NULL;
- JSContext* cx = NULL;
- JSObject* global = NULL;
- JSFunctionSpec* sp = NULL;
- const char* script_name = NULL;
- int use_http = 0;
- int i = 0;
-
- if(argc > 3)
- {
- fprintf(stderr, "ERROR: Too many arguments.\n");
- return usage();
- }
- else if(argc == 3)
- {
- if(strcmp(argv[1], "-H"))
- {
- fprintf(stderr, "ERROR: Invalid option: %s\n", argv[1]);
- return usage();
- }
- use_http = 1;
- script_name = argv[2];
- }
- else if(argc == 2 && strcmp(argv[1], "-H") == 0)
- {
- use_http = 1;
- }
- else if (argc == 2)
- {
- script_name = argv[1];
- }
- // else argc == 1, use defaults
-
- rt = JS_NewRuntime(64L * 1024L * 1024L);
- if (!rt) return 1;
-
- cx = JS_NewContext(rt, 8L * 1024L);
- if (!cx) return 1;
-
- JS_SetErrorReporter(cx, printerror);
- JS_ToggleOptions(cx, JSOPTION_XML);
-
- SETUP_REQUEST(cx);
-
- global = JS_NewObject(cx, &global_class, NULL, NULL);
- if (!global) return 1;
- if (!JS_InitStandardClasses(cx, global)) return 1;
-
- for(sp = global_functions; sp->name != NULL; sp++)
- {
- if(!JS_DefineFunction(cx, global,
- sp->name, sp->call, sp->nargs, sp->flags))
- {
- fprintf(stderr, "Failed to create function: %s\n", sp->name);
- return 1;
- }
- }
-
- if(use_http && !install_http(cx, global))
- {
- return 2;
- }
-
- JS_SetGlobalObject(cx, global);
- execute_script(cx, global, script_name);
-
- FINISH_REQUEST(cx);
-
- JS_DestroyContext(cx);
- JS_DestroyRuntime(rt);
- JS_ShutDown();
-
- return gExitCode;
-}
View
286 src/couchdb/priv/couch_js/utf8.c
@@ -1,286 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-#include <jsapi.h>
-
-static int
-enc_char(uint8 *utf8Buffer, uint32 ucs4Char)
-{
- int utf8Length = 1;
-
- if (ucs4Char < 0x80)
- {
- *utf8Buffer = (uint8)ucs4Char;
- }
- else
- {
- int i;
- uint32 a = ucs4Char >> 11;
- utf8Length = 2;
- while(a)
- {
- a >>= 5;
- utf8Length++;
- }
- i = utf8Length;
- while(--i)
- {
- utf8Buffer[i] = (uint8)((ucs4Char & 0x3F) | 0x80);
- ucs4Char >>= 6;
- }
- *utf8Buffer = (uint8)(0x100 - (1 << (8-utf8Length)) + ucs4Char);
- }
-
- return utf8Length;
-}
-
-static JSBool
-enc_charbuf(const jschar* src, size_t srclen, char* dst, size_t* dstlenp)
-{
- size_t i;
- size_t utf8Len;
- size_t dstlen = *dstlenp;
- size_t origDstlen = dstlen;
- jschar c;
- jschar c2;
- uint32 v;
- uint8 utf8buf[6];
-
- if(!dst)
- {
- dstlen = origDstlen = (size_t) -1;
- }
-
- while(srclen)
- {
- c = *src++;
- srclen--;
-
- if((c >= 0xDC00) && (c <= 0xDFFF)) goto bad_surrogate;
-
- if(c < 0xD800 || c > 0xDBFF)
- {
- v = c;
- }
- else
- {
- if(srclen < 1) goto buffer_too_small;
- c2 = *src++;
- srclen--;
- if ((c2 < 0xDC00) || (c2 > 0xDFFF))
- {
- c = c2;
- goto bad_surrogate;
- }
- v = ((c - 0xD800) << 10) + (c2 - 0xDC00) + 0x10000;
- }
- if(v < 0x0080)
- {
- /* no encoding necessary - performance hack */
- if(!dstlen) goto buffer_too_small;
- if(dst) *dst++ = (char) v;
- utf8Len = 1;
- }
- else
- {
- utf8Len = enc_char(utf8buf, v);
- if(utf8Len > dstlen) goto buffer_too_small;
- if(dst)
- {
- for (i = 0; i < utf8Len; i++)
- {
- *dst++ = (char) utf8buf[i];
- }
- }
- }
- dstlen -= utf8Len;
- }
-
- *dstlenp = (origDstlen - dstlen);
- return JS_TRUE;
-
-bad_surrogate:
- *dstlenp = (origDstlen - dstlen);
- return JS_FALSE;
-
-buffer_too_small:
- *dstlenp = (origDstlen - dstlen);
- return JS_FALSE;
-}
-
-char*
-enc_string(JSContext* cx, jsval arg, size_t* buflen)
-{
- JSString* str = NULL;
- jschar* src = NULL;
- char* bytes = NULL;
- size_t srclen = 0;
- size_t byteslen = 0;
-
- str = JS_ValueToString(cx, arg);
- if(!str) goto error;
-
- src = JS_GetStringChars(str);
- srclen = JS_GetStringLength(str);
-
- if(!enc_charbuf(src, srclen, NULL, &byteslen)) goto error;
-
- bytes = JS_malloc(cx, (byteslen) + 1);
- bytes[byteslen] = 0;
-
- if(!enc_charbuf(src, srclen, bytes, &byteslen)) goto error;
-
- if(buflen) *buflen = byteslen;
- goto success;
-
-error:
- if(bytes != NULL) JS_free(cx, bytes);
- bytes = NULL;
-
-success:
- return bytes;
-}
-
-static uint32
-dec_char(const uint8 *utf8Buffer, int utf8Length)
-{
- uint32 ucs4Char;
- uint32 minucs4Char;
-
- /* from Unicode 3.1, non-shortest form is illegal */
- static const uint32 minucs4Table[] = {
- 0x00000080, 0x00000800, 0x0001000, 0x0020000, 0x0400000
- };
-
- if (utf8Length == 1)
- {
- ucs4Char = *utf8Buffer;
- }
- else
- {
- ucs4Char = *utf8Buffer++ & ((1<<(7-utf8Length))-1);
- minucs4Char = minucs4Table[utf8Length-2];
- while(--utf8Length)
- {
- ucs4Char = ucs4Char<<6 | (*utf8Buffer++ & 0x3F);
- }
- if(ucs4Char < minucs4Char || ucs4Char == 0xFFFE || ucs4Char == 0xFFFF)
- {
- ucs4Char = 0xFFFD;
- }
- }
-
- return ucs4Char;
-}
-
-static JSBool
-dec_charbuf(const char *src, size_t srclen, jschar *dst, size_t *dstlenp)
-{
- uint32 v;
- size_t offset = 0;
- size_t j;
- size_t n;
- size_t dstlen = *dstlenp;
- size_t origDstlen = dstlen;
-
- if(!dst) dstlen = origDstlen = (size_t) -1;
-
- while(srclen)
- {
- v = (uint8) *src;
- n = 1;
-
- if(v & 0x80)
- {
- while(v & (0x80 >> n))
- {
- n++;
- }
-
- if(n > srclen) goto buffer_too_small;
- if(n == 1 || n > 6) goto bad_character;
-
- for(j = 1; j < n; j++)
- {
- if((src[j] & 0xC0) != 0x80) goto bad_character;
- }
-
- v = dec_char((const uint8 *) src, n);
- if(v >= 0x10000)
- {
- v -= 0x10000;
-
- if(v > 0xFFFFF || dstlen < 2)
- {
- *dstlenp = (origDstlen - dstlen);
- return JS_FALSE;
- }
-