├── .dockerignore ├── .github ├── release.yml └── workflows │ ├── ci-docker.yml │ ├── ci-main.yml │ ├── ci-pr.yml │ └── neo-regress.yml ├── .gitignore ├── LICENSE ├── README.md ├── api ├── append_worker.go ├── benchmark_test.go ├── benchmark_test.md ├── bridge │ ├── bridge.pb.go │ └── bridge_grpc.pb.go ├── columns.go ├── commands.go ├── data_type.go ├── do.go ├── do_ilp.go ├── do_indexes.go ├── do_license.go ├── do_query.go ├── do_tables.go ├── do_tags.go ├── do_test.go ├── errors.go ├── machcli │ ├── machcli.go │ ├── machcli_test.go │ ├── pool.go │ └── pool_test.go ├── machrpc │ ├── driver.go │ ├── machrpc.go │ ├── machrpc.pb.go │ ├── machrpc_grpc.pb.go │ ├── machrpc_test.go │ └── pbconv.go ├── machsvr │ ├── mach_append.go │ ├── mach_grpc.go │ ├── mach_rows.go │ ├── machsvr.go │ ├── machsvr_test.go │ ├── sys.go │ ├── sys_async.go │ └── sys_windows.go ├── metrics.go ├── mgmt │ ├── mgmt.pb.go │ └── mgmt_grpc.pb.go ├── options.go ├── proto │ ├── bridge.proto │ ├── machrpc.proto │ ├── mgmt.proto │ └── schedule.proto ├── scan.go ├── schedule │ ├── schedule.pb.go │ └── schedule_grpc.pb.go ├── sql_wrap.go ├── testsuite │ ├── all_test.go │ ├── columns.go │ ├── database.go │ ├── describe.go │ ├── explain.go │ ├── license.go │ ├── logtable.go │ ├── tables.go │ ├── tagtable.go │ ├── testsuite.conf │ ├── testsuite.go │ ├── users.go │ └── watch.go ├── types.go └── watch.go ├── booter ├── README.md ├── boot.go ├── boot_test.go ├── bootable.go ├── builder.go ├── cli.go ├── cli_test.go ├── daemon_unix.go ├── daemon_windows.go ├── default_svr.go ├── definitions.go ├── functions.go ├── helper.go └── test │ ├── 000_mod_env.hcl │ ├── 001_mod_amod.hcl │ ├── 002_mod_bmod.hcl │ ├── 999_mod_others.hcl │ └── main │ └── main.go ├── cspell.json ├── docs ├── charts_demo.jpg ├── dashboard.png ├── deps.png ├── screenshot01.jpg ├── screenshot02.jpg ├── screenshot03.jpg └── ui-api.md ├── examples ├── go │ ├── cli_append │ │ └── append.go │ ├── cli_exec │ │ └── exec.go │ ├── cli_execdirect │ │ └── exec_direct.go │ ├── cli_query │ │ └── query.go │ ├── cli_queryrow │ │ └── query_row.go │ ├── grpc_append │ │ └── grpc_append.go │ ├── grpc_cretable │ │ └── grpc_cretable.go │ ├── grpc_droptable │ │ └── grpc_droptable.go │ ├── grpc_insert │ │ └── grpc_insert.go │ ├── grpc_query │ │ └── grpc_query.go │ ├── grpc_queryrow │ │ └── grpc_queryrow.go │ ├── grpc_wave │ │ └── grpc_wave.go │ ├── http_get │ │ └── http_get.go │ ├── http_post_form │ │ └── http_post_form.go │ ├── http_post_query │ │ └── http_post_query.go │ ├── http_wave │ │ └── http_wave.go │ ├── http_write_csv │ │ └── http_write_csv.go │ ├── http_write_json │ │ └── http_write_json.go │ ├── mqtt_client │ │ └── mqtt_client.go │ ├── mqtt_query │ │ └── mqtt_query.go │ ├── mqtt_subscriber │ │ └── mqtt_subscriber.go │ ├── nats_jspub │ │ └── nats_jspub.go │ ├── nats_jssub │ │ └── nats_jssub.go │ ├── nats_pub │ │ └── nats_pub.go │ ├── nats_sub │ │ └── nats_sub.go │ └── sql_driver │ │ └── sql_driver.go ├── java │ └── grpc │ │ ├── pom.xml │ │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── machbase │ │ │ └── neo │ │ │ └── example │ │ │ └── Example.java │ │ └── proto │ │ └── machrpc.proto ├── javascript │ ├── http_read_csv.js │ ├── http_read_json.js │ ├── http_write_csv.js │ └── http_write_json.js └── python │ ├── http_read_csv.py │ ├── http_write_csv.py │ ├── machrpc.py │ ├── machrpc_pb2.py │ └── machrpc_pb2_grpc.py ├── go.mod ├── go.sum ├── mage.go ├── magefiles ├── magefile.go ├── neo-launcher-version.txt └── neo-web-version.txt ├── main ├── machbase-neo │ └── main.go └── neoshell │ └── main.go ├── mods ├── args │ ├── args.go │ ├── args_test.go │ ├── help.go │ ├── help_test.go │ ├── serve.go │ ├── svc_unix.go │ └── svc_windows.go ├── banner_c.txt ├── banner_m.txt ├── bridge │ ├── bridge.go │ ├── bridge_test.go │ ├── client │ │ └── client_impl.go │ ├── connector │ │ ├── connector.go │ │ ├── mssql │ │ │ └── mssql.go │ │ ├── mysql │ │ │ └── mysql.go │ │ ├── postgres │ │ │ └── postgres.go │ │ └── sqlite │ │ │ └── sqlite3.go │ ├── internal │ │ ├── base.go │ │ ├── mssql │ │ │ └── mssql.go │ │ ├── mysql │ │ │ └── mysql.go │ │ ├── postgres │ │ │ ├── postgres.go │ │ │ └── postgres_test.go │ │ └── sqlite3 │ │ │ ├── sqlite3.go │ │ │ └── sqlite3_test.go │ ├── management.go │ ├── mqtt.go │ ├── nats.go │ ├── registry.go │ ├── runtime.go │ ├── types.go │ └── util_datum.go ├── codec │ ├── builder.go │ ├── facility │ │ ├── logger.go │ │ └── volatile.go │ ├── internal │ │ ├── base.go │ │ ├── box │ │ │ ├── box_encode.go │ │ │ └── box_encode_test.go │ │ ├── chart │ │ │ ├── chart.go │ │ │ ├── chart_test.go │ │ │ ├── chartcompat.go │ │ │ ├── chartcompat_test.go │ │ │ ├── rendertpls.go │ │ │ └── test │ │ │ │ ├── anscombe_quartet.json │ │ │ │ ├── compat_bar.js │ │ │ │ ├── compat_bar.json │ │ │ │ ├── compat_line.html │ │ │ │ ├── compat_line.js │ │ │ │ ├── compat_line.json │ │ │ │ ├── compat_line3d.js │ │ │ │ ├── compat_line3d.json │ │ │ │ ├── compat_scatter.js │ │ │ │ ├── compat_scatter.json │ │ │ │ ├── mark_line.json │ │ │ │ ├── tangential_polar_bar.html │ │ │ │ ├── tangential_polar_bar.json │ │ │ │ ├── test_candlestick.json │ │ │ │ ├── test_line.html │ │ │ │ ├── test_line.js │ │ │ │ ├── test_line.json │ │ │ │ ├── test_scatter.html │ │ │ │ ├── test_scatter.js │ │ │ │ └── test_scatter.json │ │ ├── csv │ │ │ ├── csv_decode.go │ │ │ ├── csv_decode_test.go │ │ │ ├── csv_encode.go │ │ │ └── csv_encode_test.go │ │ ├── geomap │ │ │ ├── geomap.go │ │ │ ├── geomap_test.go │ │ │ ├── leafletobj.go │ │ │ ├── leafletobj_test.go │ │ │ ├── rendertpls.go │ │ │ └── test │ │ │ │ ├── geomap_polygon.html │ │ │ │ ├── geomap_test.html │ │ │ │ ├── geomap_test.js │ │ │ │ ├── geomap_test.json │ │ │ │ ├── geomap_test_geojson.html │ │ │ │ ├── geomap_test_geojson.js │ │ │ │ └── geomap_test_geojson.json │ │ ├── html │ │ │ └── html.go │ │ ├── json │ │ │ ├── json_decode.go │ │ │ ├── json_decode_test.go │ │ │ ├── json_encode.go │ │ │ ├── json_encode_test.go │ │ │ └── json_test.go │ │ ├── markdown │ │ │ ├── md_encode.go │ │ │ ├── md_test.go │ │ │ └── test │ │ │ │ ├── output_brief.html │ │ │ │ ├── output_brief.txt │ │ │ │ ├── output_md.html │ │ │ │ ├── output_md.txt │ │ │ │ ├── output_timeformat.html │ │ │ │ └── output_timeformat.txt │ │ ├── ndjson │ │ │ ├── decode.go │ │ │ ├── decode_test.go │ │ │ ├── encode.go │ │ │ └── encode_test.go │ │ └── templ │ │ │ ├── templ.go │ │ │ └── templ_test.go │ └── opts │ │ ├── generate.gen.go │ │ ├── generate.go │ │ └── opts.go ├── eventbus │ ├── bus.go │ ├── bus_test.go │ ├── eventbus.go │ ├── eventbus_test.go │ ├── message.go │ └── message_test.go ├── jsh │ ├── analysis │ │ ├── analysis.go │ │ └── analysis_test.go │ ├── builtin │ │ ├── builtin.go │ │ ├── console.go │ │ ├── generate.gen.go │ │ ├── generate.go │ │ ├── jsh.js │ │ ├── kill.js │ │ ├── ls.js │ │ ├── open.js │ │ ├── ps.js │ │ └── servicectl.js │ ├── cleaner.go │ ├── db │ │ ├── .gitignore │ │ ├── dbms.go │ │ └── dbms_test.go │ ├── filter │ │ ├── filter.go │ │ └── filter_test.go │ ├── generator │ │ ├── generator.go │ │ └── generator_test.go │ ├── http │ │ ├── client.go │ │ ├── http.go │ │ ├── http_test.go │ │ ├── listener.go │ │ ├── router.go │ │ └── test │ │ │ ├── html │ │ │ ├── hello.txt │ │ │ └── index.html │ │ │ └── tmpl │ │ │ ├── hello.html │ │ │ └── hello_tmpl.html │ ├── jsh.go │ ├── jsh_test.go │ ├── mat │ │ ├── dense.go │ │ ├── dense_test.go │ │ ├── matrix.go │ │ ├── matrix_test.go │ │ ├── qr.go │ │ ├── qr_test.go │ │ ├── symdense.go │ │ ├── symdense_test.go │ │ ├── vector.go │ │ └── vector_test.go │ ├── mqtt │ │ ├── mqtt.go │ │ └── mqtt_test.go │ ├── opcua │ │ ├── opcua.go │ │ ├── opcua_test.go │ │ └── test_server │ │ │ ├── README.md │ │ │ └── TestServer.go │ ├── process.go │ ├── psutil │ │ ├── psutil.go │ │ └── psutil_test.go │ ├── publisher │ │ └── publisher.go │ ├── readline.go │ ├── service.go │ ├── service_test.go │ ├── spatial │ │ ├── spatial.go │ │ └── spatial_test.go │ ├── system │ │ ├── system.go │ │ └── system_test.go │ └── test │ │ ├── etc_services │ │ ├── svc1.json │ │ └── wrong1.json │ │ ├── jsh-cleanup.js │ │ ├── jsh-exception.js │ │ ├── jsh-hello-world.js │ │ └── jsh-interrupt.js ├── logging │ ├── level.go │ ├── level_write.go │ ├── logging.go │ ├── module.go │ └── slog.go ├── model │ ├── bridgedef.go │ ├── model.go │ ├── scheduledef.go │ └── shelldef.go ├── nums │ ├── array.go │ ├── fakegen.go │ ├── fft │ │ ├── fft.go │ │ └── fft_test.go │ ├── geography.go │ ├── geometry.go │ ├── histogram.go │ ├── histogram_test.go │ ├── kalman │ │ ├── CREDIT.TXT │ │ ├── kalman.go │ │ ├── kalman_test.go │ │ ├── models │ │ │ ├── brownian.go │ │ │ ├── constant_velocity.go │ │ │ ├── model.go │ │ │ └── simple.go │ │ └── smooth.go │ ├── mercator.go │ ├── mercator_test.go │ ├── mercatortiles.go │ ├── mercatortiles_test.go │ ├── nums.go │ ├── nums_test.go │ ├── opensimplex │ │ ├── base.go │ │ ├── constatns.go │ │ ├── generator.go │ │ ├── generator_test.go │ │ └── test │ │ │ └── samples.json.gz │ ├── oscillator │ │ └── oscillator.go │ ├── projection.go │ ├── simplify.go │ ├── simplify_test.go │ ├── vectors.go │ └── vectors_test.go ├── pkgs │ ├── backend.go │ ├── manager.go │ └── proxy.go ├── scheduler │ ├── management.go │ ├── registry.go │ ├── sched_subs.go │ ├── sched_subs_test.go │ ├── sched_timer.go │ └── scheduler.go ├── server │ ├── assets │ │ ├── apple-touch-icon-precomposed.png │ │ ├── apple-touch-icon.png │ │ ├── echarts │ │ │ ├── bulma.min.css │ │ │ ├── echarts-gl.min.js │ │ │ ├── echarts-liquidfill.min.js │ │ │ ├── echarts-liquidfill.min.js.map │ │ │ ├── echarts-wordcloud.min.js │ │ │ ├── echarts-wordcloud.min.js.map │ │ │ ├── echarts.min.js │ │ │ └── themes │ │ │ │ ├── chalk.js │ │ │ │ ├── dark.js │ │ │ │ ├── essos.js │ │ │ │ ├── index.js │ │ │ │ ├── infographic.js │ │ │ │ ├── macarons.js │ │ │ │ ├── purple-passion.js │ │ │ │ ├── roma.js │ │ │ │ ├── romantic.js │ │ │ │ ├── shine.js │ │ │ │ ├── vintage.js │ │ │ │ ├── walden.js │ │ │ │ ├── westeros.js │ │ │ │ └── wonderland.js │ │ ├── favicon.ico │ │ ├── geomap │ │ │ ├── images │ │ │ │ ├── layers-2x.png │ │ │ │ ├── layers.png │ │ │ │ ├── marker-icon-2x.png │ │ │ │ ├── marker-icon.png │ │ │ │ └── marker-shadow.png │ │ │ ├── leaflet.css │ │ │ ├── leaflet.js │ │ │ ├── leaflet.js.map │ │ │ ├── proj4.js │ │ │ └── proj4leaflet.js │ │ └── tutorials │ │ │ ├── sample_markdown.wrk │ │ │ ├── sample_mermaid.wrk │ │ │ └── sample_pikchr.wrk │ ├── chat │ │ ├── config.go │ │ ├── dialog.go │ │ ├── dialog_claude.go │ │ ├── dialog_ollama.go │ │ ├── dialog_other.go │ │ ├── dialog_test.go │ │ └── tools.go │ ├── eula.txt │ ├── http.go │ ├── http_assets.go │ ├── http_facility.go │ ├── http_facility_test.go │ ├── http_lake.go │ ├── http_lake_test.go │ ├── http_opts.go │ ├── http_query.go │ ├── http_test.go │ ├── http_util.go │ ├── http_write.go │ ├── http_ws.go │ ├── http_ws_test.go │ ├── machbase_conf.go │ ├── machbase_template.conf │ ├── mcpsvr │ │ ├── mcpcli.go │ │ ├── mcpsvr.go │ │ └── tools.go │ ├── mqtt.go │ ├── mqtt_query.go │ ├── mqtt_test.go │ ├── mqtt_write.go │ ├── server.go │ ├── server_test.go │ ├── ssh.go │ ├── ssh_unix.go │ ├── ssh_windows.go │ ├── svrauth.go │ ├── svrbackup.go │ ├── svrconf.go │ ├── svrconf.hcl │ ├── svrgrpc.go │ ├── svrkey.go │ ├── svrkey_test.go │ ├── svrmetric.go │ ├── svrmgmt.go │ ├── svrmgmt_test.go │ ├── svrmsg.go │ ├── svrnavel.go │ ├── svrshells.go │ ├── test │ │ ├── csv_append.tql │ │ ├── csv_map.tql │ │ ├── image.png │ │ ├── test_client_cert.pem │ │ ├── test_client_key.pem │ │ ├── test_markdown_list.md │ │ ├── test_markdown_list.txt │ │ ├── test_markdown_list_utf8.md │ │ ├── test_markdown_list_utf8.txt │ │ ├── test_markdown_mermaid.md │ │ ├── test_markdown_mermaid.txt │ │ ├── test_server_cert.pem │ │ └── test_server_key.pem │ └── web │ │ └── .gitkeep ├── shell │ ├── internal │ │ ├── action │ │ │ ├── actor.go │ │ │ ├── box.go │ │ │ ├── command.go │ │ │ ├── command_test.go │ │ │ ├── complete.go │ │ │ ├── complete_helper.go │ │ │ ├── complete_runes.go │ │ │ ├── complete_test.go │ │ │ ├── context.go │ │ │ ├── help.go │ │ │ ├── history.go │ │ │ ├── parser.go │ │ │ ├── pref.go │ │ │ ├── pref_test.go │ │ │ ├── process.go │ │ │ ├── prompt.go │ │ │ └── registry.go │ │ └── cmd │ │ │ ├── bridge.go │ │ │ ├── connect.go │ │ │ ├── describe.go │ │ │ ├── explain.go │ │ │ ├── export.go │ │ │ ├── fake.go │ │ │ ├── http.go │ │ │ ├── import.go │ │ │ ├── key.go │ │ │ ├── ping.go │ │ │ ├── run.go │ │ │ ├── session.go │ │ │ ├── set.go │ │ │ ├── shell.go │ │ │ ├── show.go │ │ │ ├── shutdown.go │ │ │ ├── sql.go │ │ │ ├── ssh-key.go │ │ │ ├── subscriber.go │ │ │ └── timer.go │ └── main.go ├── tql │ ├── conv.go │ ├── conv_test.go │ ├── fm_bytes.go │ ├── fm_context.go │ ├── fm_csv.go │ ├── fm_dbsink.go │ ├── fm_dbsrc.go │ ├── fm_encoder.go │ ├── fm_fake.go │ ├── fm_fourier.go │ ├── fm_monad.go │ ├── fm_script.go │ ├── fm_script_test.go │ ├── fm_shell.go │ ├── fm_stat.go │ ├── fm_stat_test.go │ ├── fm_time.go │ ├── func_test.go │ ├── fx_codec_opts.gen.go │ ├── fx_definitions.go │ ├── fx_generate.gen.go │ ├── fx_generate.go │ ├── internal │ │ └── expression │ │ │ ├── benchmark_test.go │ │ │ ├── dummy_test.go │ │ │ ├── evaluation.go │ │ │ ├── evaluation_test.go │ │ │ ├── evaluationfail_test.go │ │ │ ├── expression.go │ │ │ ├── expression_sql.go │ │ │ ├── expression_sql_test.go │ │ │ ├── expression_test.go │ │ │ ├── lexer.go │ │ │ ├── lexer_state.go │ │ │ ├── operator.go │ │ │ ├── parameter.go │ │ │ ├── parse.go │ │ │ ├── parse_test.go │ │ │ ├── stage.go │ │ │ └── token.go │ ├── loader.go │ ├── task.go │ ├── task_node.go │ ├── task_output.go │ ├── task_record.go │ ├── task_test.go │ ├── test │ │ ├── TestLoader.csv │ │ ├── TestLoader.tql │ │ ├── TestLoader_Pi.csv │ │ ├── TestLoader_Pi.tql │ │ ├── TestLoader_group.csv │ │ ├── TestLoader_group.tql │ │ ├── TestLoader_groupbykey.csv │ │ ├── TestLoader_groupbykey.tql │ │ ├── TestLoader_iris.csv │ │ ├── TestLoader_iris.tql │ │ ├── TestLoader_iris_setosa.csv │ │ ├── TestLoader_iris_setosa.tql │ │ ├── TestLoader_qq.csv │ │ ├── TestLoader_qq.tql │ │ ├── TestLoader_simplex.csv │ │ ├── TestLoader_simplex.tql │ │ ├── euc-jp.csv │ │ ├── fft2d.csv │ │ ├── fft3d.csv │ │ ├── html_template_item.html │ │ ├── html_template_list.html │ │ ├── iris.data │ │ ├── iris.data.gz │ │ ├── js-geojson-point.js │ │ ├── js-geojson-polygon.js │ │ ├── lines.txt │ │ ├── markdown_xhtml.txt │ │ ├── movavg_result.csv │ │ ├── movavg_result_nowait.csv │ │ ├── oscillator_1.txt │ │ ├── oscillator_1Hz_2Hz_3Hz.csv │ │ ├── sphere_0_0.csv │ │ ├── sphere_4_4.csv │ │ ├── sql_ddl_executed.txt │ │ ├── transpose_all.csv │ │ ├── transpose_all.tql │ │ ├── transpose_all_hdr.csv │ │ ├── transpose_all_hdr.tql │ │ ├── transpose_hdr.csv │ │ ├── transpose_hdr.tql │ │ ├── transpose_nohdr.csv │ │ └── transpose_nohdr.tql │ ├── tql_pragma_test.go │ ├── tql_test.go │ ├── tqlcache.go │ ├── tqlcache_test.go │ ├── tqlreader.go │ └── tqlreader_test.go ├── util │ ├── charset │ │ ├── charset.go │ │ └── charset_test.go │ ├── conpty │ │ ├── conpty.go │ │ ├── exec.go │ │ └── syscall.go │ ├── crypto.go │ ├── crypto_test.go │ ├── defaults.go │ ├── defaults_test.go │ ├── files.go │ ├── format.go │ ├── format_test.go │ ├── glob │ │ ├── glob.go │ │ ├── glob_test.go │ │ └── match.go │ ├── humanize.go │ ├── humanize_test.go │ ├── ini │ │ ├── escape.go │ │ ├── ini.go │ │ ├── ini_test.go │ │ ├── key.go │ │ ├── loader.go │ │ ├── resolve.go │ │ └── section.go │ ├── interface.go │ ├── interface_test.go │ ├── jemalloc │ │ ├── jemalloc.go │ │ ├── jemalloc_none.go │ │ └── shared.go │ ├── mdconv │ │ ├── d2ext │ │ │ ├── ast.go │ │ │ ├── extender.go │ │ │ ├── renderer.go │ │ │ └── transformer.go │ │ ├── mdconv.go │ │ └── mdconv_test.go │ ├── metric │ │ ├── _example │ │ │ └── simple.go │ │ ├── counter.go │ │ ├── counter_test.go │ │ ├── dashboard.go │ │ ├── dashboard.tmpl │ │ ├── dashboard_test.go │ │ ├── filters.go │ │ ├── filters_test.go │ │ ├── gauge.go │ │ ├── gauge_test.go │ │ ├── histogram.go │ │ ├── histogram_test.go │ │ ├── input │ │ │ └── runtime.go │ │ ├── meter.go │ │ ├── meter_test.go │ │ ├── metric.go │ │ ├── metric_test.go │ │ ├── odometer.go │ │ ├── odometer_test.go │ │ ├── output │ │ │ └── machcli.go │ │ ├── store.go │ │ ├── timer.go │ │ ├── timer_test.go │ │ ├── timeseries.go │ │ ├── timeseries_test.go │ │ ├── value.go │ │ └── value_derivers.go │ ├── osutils.go │ ├── osutils_test.go │ ├── osutils_unix.go │ ├── osutils_windows.go │ ├── restclient │ │ ├── parser.go │ │ ├── parser_test.go │ │ ├── restclient.go │ │ ├── restclient_test.go │ │ └── test │ │ │ ├── 1.json │ │ │ ├── 1.png │ │ │ └── 1.xml │ ├── scheduler.go │ ├── shortcuts.go │ ├── shutdownhook.go │ ├── snowflake │ │ ├── default.go │ │ ├── snowflake.go │ │ └── snowflake_test.go │ ├── split.go │ ├── split_test.go │ ├── ssfs │ │ ├── ssfs.go │ │ ├── ssfs_test.go │ │ ├── test │ │ │ ├── data1 │ │ │ │ └── simple.tql │ │ │ ├── notaccess.tql │ │ │ └── root │ │ │ │ ├── example.tql │ │ │ │ ├── hello.sql │ │ │ │ └── select.sql │ │ └── virtuals.go │ ├── stream.go │ ├── strpad.go │ ├── strpad_test.go │ ├── testdata │ │ ├── splitter_sql_1.json │ │ ├── splitter_sql_1.sql │ │ ├── splitter_sql_2.json │ │ └── splitter_sql_2.sql │ ├── time_formats.go │ ├── time_locations.go │ ├── time_locations_test.go │ ├── types.go │ ├── types_test.go │ ├── writepath.go │ ├── writepath_test.go │ └── ymd │ │ ├── ymd.go │ │ └── ymd_test.go ├── versions.go └── versions_test.go ├── scripts ├── CentOS7.Dockerfile ├── CentOS7_repo.txt ├── Dockerfile ├── Ubuntu18.04.Dockerfile ├── Ubuntu24.04.Dockerfile ├── Ubuntu_arm32v7.Dockerfile ├── machbase-arm32v7.Dockerfile └── machbase-neo.Dockerfile └── test ├── bench_test.go ├── grpc_log_test.go ├── grpc_tag_test.go ├── http_query.http ├── mqttsvr_test.go └── svr_test.go /.dockerignore: -------------------------------------------------------------------------------- 1 | tmp/** 2 | packages/** 3 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | # .github/release.yml 2 | 3 | changelog: 4 | categories: 5 | - title: 🎉 Features 6 | labels: 7 | - '*' 8 | exclude: 9 | labels: 10 | - dependencies 11 | - title: 👒 Dependencies 12 | labels: 13 | - dependencies 14 | -------------------------------------------------------------------------------- /.github/workflows/ci-pr.yml: -------------------------------------------------------------------------------- 1 | name: CI-PR 2 | 3 | on: 4 | pull_request: 5 | types: [ opened, synchronize, reopened ] 6 | 7 | jobs: 8 | build: 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | platform: 13 | - macos-15 14 | - macos-14 15 | - macos-13 16 | - ubuntu-22.04 17 | - ubuntu-22.04-arm 18 | - ubuntu-24.04 19 | - ubuntu-24.04-arm 20 | - windows-2022 21 | runs-on: ${{ matrix.platform }} 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v4 25 | with: 26 | fetch-depth: 0 27 | - name: Setup MINGW64 28 | if: startsWith(matrix.platform, 'windows') 29 | uses: msys2/setup-msys2@v2 30 | with: 31 | msystem: mingw64 32 | install: >- 33 | mingw-w64-x86_64-gcc 34 | - name: Setup go compiler 35 | uses: actions/setup-go@v5 36 | with: 37 | go-version-file: 'go.mod' 38 | check-latest: true 39 | cache: false 40 | - name: Setup zig 41 | if: matrix.platform == 'ubuntu-22.04' 42 | uses: goto-bus-stop/setup-zig@v2 43 | with: 44 | version: 0.11.0 45 | - name: Test 46 | run: go run mage.go test 47 | - name: Codecov 48 | if: matrix.platform == 'ubuntu-22.04' 49 | uses: codecov/codecov-action@v5 50 | env: 51 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 52 | - name: Test arm32 53 | if: matrix.platform == 'ubuntu-22.04' 54 | run: go run mage.go buildx machbase-neo linux arm 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | /.pnp 4 | .pnp.js 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | .vscode/ 19 | examples/**/target 20 | 21 | .config/ 22 | 23 | tmp/ 24 | packages/ 25 | vendor/ 26 | examples/**/target 27 | testsuite_tmp/ 28 | 29 | mods/service/httpd/web/ui/* 30 | mods/server/web/ui/* 31 | mach-grpc.sock 32 | 33 | native/**/*_dummy.o 34 | 35 | # mage temp file 36 | mage_output_file.go 37 | -------------------------------------------------------------------------------- /api/benchmark_test.go: -------------------------------------------------------------------------------- 1 | package api_test 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | "testing" 8 | "time" 9 | 10 | "github.com/machbase/neo-server/v8/api" 11 | "github.com/machbase/neo-server/v8/api/testsuite" 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func BenchmarkTagDataAppend(b *testing.B) { 16 | db := testsuite.Database_machsvr(b) 17 | 18 | ctx := context.TODO() 19 | conn, err := db.Connect(ctx, api.WithTrustUser("sys")) 20 | require.NoError(b, err, "connect fail") 21 | defer conn.Close() 22 | 23 | appender, err := conn.Appender(ctx, "tag_data") 24 | require.NoError(b, err, "appender fail") 25 | defer appender.Close() 26 | 27 | for i := 0; i < b.N; i++ { 28 | err = appender.Append( 29 | fmt.Sprintf("append-bench-%d", i%100), 30 | time.Now().UnixNano(), 31 | 1.001*float64(i+1), 32 | int16(i), 33 | uint16(i), 34 | int32(i), 35 | uint32(i), 36 | int64(i), 37 | uint64(i), 38 | fmt.Sprintf("str_value-%d", i), 39 | `{"t":"json"}`, 40 | net.IP([]byte{0x7f, 0x00, 0x00, 0x01}), 41 | net.IP([]byte{0x7f, 0x00, 0x00, 0x01}), 42 | ) 43 | require.NoError(b, err, "append fail") 44 | } 45 | } 46 | 47 | func BenchmarkTagSimpleAppend(b *testing.B) { 48 | db := testsuite.Database_machsvr(b) 49 | 50 | ctx := context.TODO() 51 | conn, err := db.Connect(ctx, api.WithTrustUser("sys")) 52 | require.NoError(b, err, "connect fail") 53 | defer conn.Close() 54 | 55 | appender, err := conn.Appender(ctx, "tag_simple") 56 | require.NoError(b, err, "appender fail") 57 | defer appender.Close() 58 | 59 | for i := 0; i < b.N; i++ { 60 | err = appender.Append( 61 | "bench-append", 62 | time.Now().UnixNano(), 63 | 1.001*float64(i+1), 64 | ) 65 | require.NoError(b, err, "append fail") 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /api/benchmark_test.md: -------------------------------------------------------------------------------- 1 | # Benchmark 2 | 3 | ## Schema 4 | 5 | ```sql 6 | create tag table tag_data( 7 | name varchar(100) primary key, 8 | time datetime basetime, 9 | value double, 10 | short_value short, 11 | ushort_value ushort, 12 | int_value integer, 13 | uint_value uinteger, 14 | long_value long, 15 | ulong_value ulong, 16 | str_value varchar(400), 17 | json_value json, 18 | ipv4_value ipv4, 19 | ipv6_value ipv6 20 | ) 21 | 22 | create tag table tag_simple( 23 | name varchar(100) primary key, 24 | time datetime basetime, 25 | value double 26 | ) 27 | ``` 28 | 29 | ## Benchmark 30 | 31 | - 2024.10.31 32 | 33 | ```sh 34 | go test -benchmem -run=^$ -bench . github.com/machbase/neo-server/v8/api 35 | 36 | goos: darwin 37 | goarch: arm64 38 | pkg: github.com/machbase/neo-server/api 39 | cpu: Apple M1 40 | BenchmarkTagDataAppend-8 59035 17203 ns/op 432 B/op 20 allocs/op 41 | BenchmarkTagSimpleAppend-8 1715670 669.2 ns/op 80 B/op 4 allocs/op 42 | ``` -------------------------------------------------------------------------------- /api/machcli/machcli_test.go: -------------------------------------------------------------------------------- 1 | package machcli_test 2 | 3 | import ( 4 | "context" 5 | _ "embed" 6 | "os" 7 | "testing" 8 | 9 | "github.com/machbase/neo-server/v8/api" 10 | "github.com/machbase/neo-server/v8/api/testsuite" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | var testServer *testsuite.Server 15 | 16 | func TestMain(m *testing.M) { 17 | testServer = testsuite.NewServer("./testsuite_tmp") 18 | testServer.StartServer(m) 19 | code := m.Run() 20 | testServer.StopServer(m) 21 | os.Exit(code) 22 | } 23 | 24 | func TestAll(t *testing.T) { 25 | testServer.CreateTestTables() 26 | testsuite.TestAll(t, testServer.DatabaseCLI(), tcTrustUser) 27 | testServer.DropTestTables() 28 | } 29 | 30 | func tcTrustUser(t *testing.T) { 31 | ctx := context.Background() 32 | db := testServer.DatabaseCLI() 33 | ok, _, err := db.UserAuth(ctx, "sys", "manager") 34 | require.NoError(t, err) 35 | require.True(t, ok) 36 | 37 | conn, err := db.Connect(ctx, api.WithTrustUser("sys")) 38 | require.NoError(t, err) 39 | require.NotNil(t, conn) 40 | err = conn.Close() 41 | require.NoError(t, err) 42 | } 43 | -------------------------------------------------------------------------------- /api/machsvr/sys.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package machsvr 5 | 6 | func translateCodePage(str string) string { 7 | return str 8 | } 9 | -------------------------------------------------------------------------------- /api/machsvr/sys_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package machsvr 5 | 6 | import ( 7 | "golang.org/x/sys/windows" 8 | "golang.org/x/text/encoding/japanese" 9 | "golang.org/x/text/encoding/korean" 10 | ) 11 | 12 | func translateCodePage(str string) string { 13 | switch windows.GetACP() { 14 | case 949: 15 | ub, _ := korean.EUCKR.NewEncoder().Bytes([]byte(str)) 16 | return string(ub) 17 | case 932: 18 | ub, _ := japanese.ShiftJIS.NewEncoder().Bytes([]byte(str)) 19 | return string(ub) 20 | default: 21 | return str 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /api/testsuite/all_test.go: -------------------------------------------------------------------------------- 1 | package testsuite_test 2 | 3 | import ( 4 | "context" 5 | _ "embed" 6 | "os" 7 | "runtime" 8 | "testing" 9 | "time" 10 | 11 | "github.com/machbase/neo-server/v8/api" 12 | "github.com/machbase/neo-server/v8/api/testsuite" 13 | ) 14 | 15 | var testServer *testsuite.Server 16 | 17 | func TestMain(m *testing.M) { 18 | testServer = testsuite.NewServer("./testsuite_tmp") 19 | testServer.StartServer(m) 20 | code := m.Run() 21 | testServer.StopServer(m) 22 | os.Exit(code) 23 | } 24 | 25 | func TestAll(t *testing.T) { 26 | for _, db := range []api.Database{ 27 | testServer.DatabaseSVR(), 28 | testServer.DatabaseRPC(), 29 | testServer.DatabaseCLI(), 30 | } { 31 | if err := testsuite.CreateTestTables(db); err != nil { 32 | t.Fatalf("ERROR: %s", err) 33 | } 34 | testsuite.TestAll(t, db) 35 | if err := testsuite.DropTestTables(db); err != nil { 36 | t.Fatalf("ERROR: %s", err) 37 | } 38 | if runtime.GOOS == "windows" { 39 | // workaround for windows, it crash randomly when closing a connection of "drop table" 40 | time.Sleep(10 * time.Second) 41 | } 42 | } 43 | } 44 | 45 | func TestColumns(t *testing.T) { 46 | db := testServer.DatabaseSVR() 47 | if err := testsuite.CreateTestTables(db); err != nil { 48 | t.Fatalf("ERROR: %s", err) 49 | } 50 | testsuite.ColumnsCases(t, db, context.TODO()) 51 | if err := testsuite.DropTestTables(db); err != nil { 52 | t.Fatalf("ERROR: %s", err) 53 | } 54 | if runtime.GOOS == "windows" { 55 | // workaround for windows, it crash randomly when closing a connection of "drop table" 56 | time.Sleep(10 * time.Second) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /api/testsuite/database.go: -------------------------------------------------------------------------------- 1 | package testsuite 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | 8 | "github.com/machbase/neo-server/v8/api" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func UserAuth(t *testing.T, db api.Database, ctx context.Context) { 13 | ok, reason, err := db.UserAuth(ctx, "sys", "mm") 14 | if err != nil { 15 | t.Fatalf("UserAuth failed [%T]: %s", db, err.Error()) 16 | } 17 | require.NoError(t, err) 18 | require.False(t, ok) 19 | require.Equal(t, "invalid username or password", reason) 20 | 21 | ok, reason, err = db.UserAuth(ctx, "sys", "manager") 22 | if err != nil { 23 | t.Fatalf("UserAuth failed: %s", err.Error()) 24 | } 25 | require.NoError(t, err) 26 | require.True(t, ok) 27 | require.Equal(t, "", reason) 28 | } 29 | 30 | func Ping(t *testing.T, db api.Database, ctx context.Context) { 31 | dur, err := db.Ping(ctx) 32 | require.NoError(t, err) 33 | require.GreaterOrEqual(t, dur, time.Duration(0)) 34 | } 35 | -------------------------------------------------------------------------------- /api/testsuite/explain.go: -------------------------------------------------------------------------------- 1 | package testsuite 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/machbase/neo-server/v8/api" 9 | "github.com/machbase/neo-server/v8/api/machcli" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func Explain(t *testing.T, db api.Database, ctx context.Context) { 14 | conn, err := db.Connect(ctx, api.WithPassword("sys", "manager")) 15 | if err != nil { 16 | t.Fatal(err) 17 | } 18 | defer conn.Close() 19 | plan, err := conn.Explain(ctx, "select * from TAG_DATA order by time desc", false) 20 | require.Nil(t, err) 21 | require.True(t, len(plan) > 0) 22 | require.True(t, strings.HasPrefix(plan, " PROJECT")) 23 | require.True(t, strings.Contains(plan, "KEYVALUE FULL SCAN")) 24 | require.True(t, strings.Contains(plan, "VOLATILE FULL SCAN")) 25 | } 26 | 27 | func ExplainFull(t *testing.T, db api.Database, ctx context.Context) { 28 | conn, err := db.Connect(ctx, api.WithPassword("sys", "manager")) 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | defer conn.Close() 33 | plan, err := conn.Explain(ctx, "select * from TAG_DATA order by time desc", true) 34 | require.Nil(t, err) 35 | require.True(t, len(plan) > 0) 36 | require.True(t, strings.Contains(plan, "********")) 37 | if _, ok := db.(*machcli.Database); ok { 38 | require.True(t, strings.Contains(plan, " NAME COUNT ACCUM(ms) AVG(ms)")) 39 | } else { 40 | require.True(t, strings.Contains(plan, " NAME COUNT ACCUMULATE(ms) AVERAGE(ms)")) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /api/testsuite/license.go: -------------------------------------------------------------------------------- 1 | package testsuite 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/machbase/neo-server/v8/api" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func License(t *testing.T, db api.Database, ctx context.Context) { 12 | conn, err := db.Connect(ctx, api.WithPassword("sys", "manager")) 13 | require.NoError(t, err, "connect fail") 14 | defer conn.Close() 15 | 16 | lic, err := api.GetLicenseInfo(ctx, conn) 17 | require.NoError(t, err, "license fail") 18 | require.Equal(t, "00000000", lic.Id) 19 | require.Equal(t, "COMMUNITY", lic.Type) 20 | require.Equal(t, "NONE", lic.Customer) 21 | require.Equal(t, "NONE", lic.Project) 22 | require.Equal(t, "KR", lic.CountryCode) 23 | require.NotEmpty(t, lic.InstallDate) 24 | require.NotEmpty(t, lic.IssueDate) 25 | require.NotEmpty(t, lic.LicenseStatus) 26 | } 27 | -------------------------------------------------------------------------------- /api/testsuite/users.go: -------------------------------------------------------------------------------- 1 | package testsuite 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | 8 | "github.com/machbase/neo-server/v8/api" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func DemoUser(t *testing.T, db api.Database, ctx context.Context) { 13 | sysConn, err := db.Connect(ctx, api.WithPassword("sys", "manager")) 14 | require.NoError(t, err, "connect fail") 15 | defer sysConn.Close() 16 | 17 | result := sysConn.Exec(ctx, "CREATE USER demo IDENTIFIED BY demo") 18 | require.NoError(t, result.Err()) 19 | defer func() { 20 | result := sysConn.Exec(ctx, "DROP table demo.TAG_DATA") 21 | require.NoError(t, result.Err()) 22 | result = sysConn.Exec(ctx, "DROP USER demo") 23 | require.NoError(t, result.Err()) 24 | }() 25 | 26 | // create table 27 | conn, err := db.Connect(ctx, api.WithPassword("demo", "demo")) 28 | require.NoError(t, err, "connect fail") 29 | defer conn.Close() 30 | 31 | result = conn.Exec(ctx, "CREATE TAG TABLE tag_data (name VARCHAR(100) primary key, time datetime basetime, value double, json_value json)") 32 | require.NoError(t, result.Err()) 33 | 34 | now, _ := time.ParseInLocation("2006-01-02 15:04:05", "2021-01-01 00:00:00", time.UTC) 35 | // insert tag_data 36 | result = conn.Exec(ctx, `insert into tag_data values('demo-1', ?, 1.23, '{"key1": "value1"}')`, now) 37 | require.NoError(t, result.Err(), "insert fail") 38 | 39 | // insert demo.tag_data 40 | result = sysConn.Exec(ctx, `insert into demo.tag_data values('demo-1', ?, 1.23, '{"key1": "value1"}')`, now.Add(1)) 41 | require.NoError(t, result.Err(), "insert fail") 42 | 43 | result = sysConn.Exec(ctx, "exec table_flush(demo.tag_data)") 44 | require.NoError(t, result.Err(), "table_flush fail") 45 | 46 | row := sysConn.QueryRow(ctx, "select count(*) from demo.tag_data where name = ?", "demo-1") 47 | require.NoError(t, row.Err()) 48 | var count int 49 | row.Scan(&count) 50 | require.Equal(t, 2, count) 51 | } 52 | -------------------------------------------------------------------------------- /booter/bootable.go: -------------------------------------------------------------------------------- 1 | package booter 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | type Boot interface { 9 | Start() error 10 | Stop() 11 | } 12 | 13 | type BootFactory struct { 14 | Id string 15 | NewConfig func() any 16 | NewInstance func(config any) (Boot, error) 17 | } 18 | 19 | var factoryRegistry = make(map[string]*BootFactory) 20 | var factoryRegistryLock sync.Mutex 21 | 22 | func RegisterBootFactory(def *BootFactory) { 23 | factoryRegistryLock.Lock() 24 | if _, exists := factoryRegistry[def.Id]; !exists { 25 | factoryRegistry[def.Id] = def 26 | } 27 | factoryRegistryLock.Unlock() 28 | } 29 | 30 | func UnregisterBootFactory(moduleId string) { 31 | delete(factoryRegistry, moduleId) 32 | } 33 | 34 | func getFactory(moduleId string) *BootFactory { 35 | if obj, ok := factoryRegistry[moduleId]; ok { 36 | return obj 37 | } 38 | return nil 39 | } 40 | 41 | func Register[T any](moduleId string, configFactory func() T, factory func(conf T) (Boot, error)) { 42 | RegisterBootFactory(&BootFactory{ 43 | Id: moduleId, 44 | NewConfig: func() any { 45 | return configFactory() 46 | }, 47 | NewInstance: func(conf any) (Boot, error) { 48 | if c, ok := conf.(T); ok { 49 | return factory(c) 50 | } else { 51 | return nil, fmt.Errorf("invalid config type: %T", conf) 52 | } 53 | }, 54 | }) 55 | } 56 | -------------------------------------------------------------------------------- /booter/daemon_unix.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package booter 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | 10 | "github.com/sevlyar/go-daemon" 11 | ) 12 | 13 | func Daemonize(bootlog string, pidfile string, proc func()) { 14 | workDir, err := os.Getwd() 15 | if err != nil { 16 | panic(err) 17 | } 18 | context := daemon.Context{ 19 | LogFileName: bootlog, 20 | LogFilePerm: 0644, 21 | WorkDir: workDir, 22 | Umask: 027, 23 | } 24 | 25 | child, err := context.Reborn() 26 | if err != nil { 27 | panic(err) 28 | } 29 | if child != nil { 30 | // post-work in parent 31 | if len(pidfile) > 0 { 32 | pfile, err := os.OpenFile(pidfile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) 33 | if err != nil { 34 | fmt.Println(err.Error()) 35 | } 36 | pfile.WriteString(fmt.Sprintf("%d", child.Pid)) 37 | pfile.Close() 38 | } 39 | return 40 | } else { 41 | // post-work in child 42 | defer func() { 43 | if err := context.Release(); err != nil { 44 | fmt.Printf("Unable to release pid-file %s, %s\n", pidfile, err.Error()) 45 | } 46 | }() 47 | proc() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /booter/daemon_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package booter 5 | 6 | func Daemonize(bootlog string, pidfile string, proc func()) { 7 | proc() 8 | } 9 | -------------------------------------------------------------------------------- /booter/test/000_mod_env.hcl: -------------------------------------------------------------------------------- 1 | define GLOBAL { 2 | IP_BIND = "127.0.0.1" 3 | IP_ADVERTISE = "10.10.10.1" 4 | 5 | SERVER_CERT = "./test/test_server_cert.pem" 6 | SERVER_KEY = "./test/test_server_key.pem" 7 | 8 | VERSION = customfunc() 9 | 10 | LOGDIR = "./tmp" 11 | LOG_LEVEL = "ERROR" 12 | LOG_PREFIX_WIDTH = 51 13 | LOG_APPEND = true 14 | 15 | BASE_PRIORITY_APP = 200 16 | } 17 | 18 | define anyname { 19 | MAX_BACKUPS = 3 20 | ROTATE = "@midnight" 21 | } 22 | -------------------------------------------------------------------------------- /booter/test/001_mod_amod.hcl: -------------------------------------------------------------------------------- 1 | 2 | module "github.com/booter/amod" { 3 | name = "amod" 4 | priority = GLOBAL_BASE_PRIORITY_APP + 1 5 | config { 6 | Version = GLOBAL_VERSION 7 | Timeout = "100ms" 8 | Dur24h = "24h" 9 | Dur2h = "2h" 10 | TcpConfig { 11 | ListenAddress = "${GLOBAL_IP_BIND}:1884" 12 | AdvertiseAddress = "mqtts://${GLOBAL_IP_ADVERTISE}:1884" 13 | SoLinger = 0 14 | KeepAlive = 10 15 | NoDelay = true 16 | Tls { 17 | LoadSystemCAs = false 18 | LoadPrivateCAs = true 19 | CertFile = GLOBAL_SERVER_CERT 20 | KeyFile = GLOBAL_SERVER_KEY 21 | HandshakeTimeout = "5s" // equivalent 5000000000 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /booter/test/002_mod_bmod.hcl: -------------------------------------------------------------------------------- 1 | module "github.com/booter/bmod" { 2 | name = "bmod" 3 | priority = GLOBAL_BASE_PRIORITY_APP + 2 4 | disabled = false 5 | config { 6 | Filename = "${env("HOME", ".")}/${GLOBAL_LOGDIR}/cmqd00.log" 7 | Append = GLOBAL_LOG_APPEND 8 | MaxBackups = anyname_MAX_BACKUPS 9 | RotateSchedule = lower(anyname_ROTATE) 10 | DefaultLevel = flagOrError("--logging-default-level") 11 | DefaultPrefixWidth = flag("--logging-default-prefix-width", GLOBAL_LOG_PREFIX_WIDTH) 12 | DefaultEnableSourceLocation = flag("--logging-default-enable-source-location", true) 13 | Levels = [ 14 | { Pattern="MCH_*", Level="DEBUG" }, 15 | { Pattern="proc", Level="TRACE" }, 16 | { Pattern="cemlib", Level="TRACE" }, 17 | ] 18 | } 19 | // reference field "Bmod" infered by camel case of module name "bmod" 20 | inject amod Bmod {} 21 | 22 | // explicitly assigned field "OtherNameForBmod" 23 | inject amod OtherNameForBmod {} 24 | } 25 | -------------------------------------------------------------------------------- /booter/test/999_mod_others.hcl: -------------------------------------------------------------------------------- 1 | 2 | module "github.com/booter/other" { 3 | disabled = true 4 | priority = GLOBAL_BASE_PRIORITY_APP+10 5 | config { 6 | Config = "../../test/other/config.ini" 7 | } 8 | } -------------------------------------------------------------------------------- /docs/charts_demo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/machbase/neo-server/93d7c126c0438af5f830eeba6e963de91da9185b/docs/charts_demo.jpg -------------------------------------------------------------------------------- /docs/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/machbase/neo-server/93d7c126c0438af5f830eeba6e963de91da9185b/docs/dashboard.png -------------------------------------------------------------------------------- /docs/deps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/machbase/neo-server/93d7c126c0438af5f830eeba6e963de91da9185b/docs/deps.png -------------------------------------------------------------------------------- /docs/screenshot01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/machbase/neo-server/93d7c126c0438af5f830eeba6e963de91da9185b/docs/screenshot01.jpg -------------------------------------------------------------------------------- /docs/screenshot02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/machbase/neo-server/93d7c126c0438af5f830eeba6e963de91da9185b/docs/screenshot02.jpg -------------------------------------------------------------------------------- /docs/screenshot03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/machbase/neo-server/93d7c126c0438af5f830eeba6e963de91da9185b/docs/screenshot03.jpg -------------------------------------------------------------------------------- /examples/go/cli_append/append.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/machbase/neo-server/v8/api" 9 | "github.com/machbase/neo-server/v8/api/machcli" 10 | ) 11 | 12 | const ( 13 | machPort = 5656 14 | machHost = "127.0.0.1" 15 | machUser = "SYS" 16 | machPass = "MANAGER" 17 | 18 | tableName = "example" 19 | tagName = "hello-world" 20 | ) 21 | 22 | func main() { 23 | db, err := machcli.NewDatabase(&machcli.Config{Host: machHost, Port: machPort}) 24 | if err != nil { 25 | panic(err) 26 | } 27 | defer db.Close() 28 | 29 | ctx := context.TODO() 30 | 31 | conn, err := db.Connect(ctx, api.WithPassword(machUser, machPass)) 32 | if err != nil { 33 | panic(err) 34 | } 35 | defer conn.Close() 36 | 37 | appender, err := conn.Appender(ctx, tableName) 38 | if err != nil { 39 | panic(err) 40 | } 41 | 42 | count := 10000 43 | ts := time.Now().Add(time.Duration(time.Duration(-1*count) * time.Second)) 44 | for i := 0; i < 10000; i++ { 45 | if i%1000 == 0 { 46 | if flusher, ok := appender.(api.Flusher); ok { 47 | if err := flusher.Flush(); err != nil { 48 | panic(err) 49 | } 50 | } 51 | } 52 | if err := appender.Append(tagName, ts.Add(time.Duration(i)*time.Second), i); err != nil { 53 | panic(err) 54 | } 55 | } 56 | s, f, err := appender.Close() 57 | if err != nil { 58 | panic(err) 59 | } 60 | fmt.Println(">> success:", s, ", fail:", f) 61 | } 62 | -------------------------------------------------------------------------------- /examples/go/cli_exec/exec.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/machbase/neo-server/v8/api" 8 | "github.com/machbase/neo-server/v8/api/machcli" 9 | ) 10 | 11 | const ( 12 | machPort = 5656 13 | machHost = "127.0.0.1" 14 | machUser = "SYS" 15 | machPass = "MANAGER" 16 | 17 | tableName = "example" 18 | ) 19 | 20 | func main() { 21 | db, err := machcli.NewDatabase(&machcli.Config{Host: machHost, Port: machPort}) 22 | if err != nil { 23 | panic(err) 24 | } 25 | defer db.Close() 26 | 27 | ctx := context.TODO() 28 | 29 | conn, err := db.Connect(ctx, api.WithPassword(machUser, machPass)) 30 | if err != nil { 31 | panic(err) 32 | } 33 | defer conn.Close() 34 | 35 | result := conn.Exec(ctx, fmt.Sprintf(` 36 | create tag table if not exists %s ( 37 | name varchar(200) primary key, 38 | time datetime basetime, 39 | value double summarized 40 | )`, tableName)) 41 | if err := result.Err(); err != nil { 42 | panic(err) 43 | } 44 | fmt.Println(">> result:", result) 45 | } 46 | -------------------------------------------------------------------------------- /examples/go/cli_execdirect/exec_direct.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/machbase/neo-server/v8/api" 8 | "github.com/machbase/neo-server/v8/api/machcli" 9 | ) 10 | 11 | const ( 12 | machPort = 5656 13 | machHost = "127.0.0.1" 14 | machUser = "SYS" 15 | machPass = "MANAGER" 16 | 17 | tableName = "example" 18 | ) 19 | 20 | func main() { 21 | db, err := machcli.NewDatabase(&machcli.Config{Host: machHost, Port: machPort}) 22 | if err != nil { 23 | panic(err) 24 | } 25 | defer db.Close() 26 | 27 | ctx := context.TODO() 28 | 29 | conn, err := db.Connect(ctx, api.WithPassword(machUser, machPass)) 30 | if err != nil { 31 | panic(err) 32 | } 33 | defer conn.Close() 34 | 35 | result := conn.Exec(ctx, fmt.Sprintf(` 36 | create tag table if not exists %s ( 37 | name varchar(200) primary key, 38 | time datetime basetime, 39 | value double summarized 40 | )`, tableName)) 41 | if err := result.Err(); err != nil { 42 | panic(err) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /examples/go/cli_query/query.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/machbase/neo-server/v8/api" 8 | "github.com/machbase/neo-server/v8/api/machcli" 9 | ) 10 | 11 | const ( 12 | machPort = 5656 13 | machHost = "127.0.0.1" 14 | machUser = "SYS" 15 | machPass = "MANAGER" 16 | 17 | tableName = "example" 18 | tagName = "helloworld" 19 | ) 20 | 21 | func main() { 22 | db, err := machcli.NewDatabase(&machcli.Config{Host: machHost, Port: machPort}) 23 | if err != nil { 24 | panic(err) 25 | } 26 | defer db.Close() 27 | 28 | ctx := context.TODO() 29 | 30 | conn, err := db.Connect(ctx, api.WithPassword(machUser, machPass)) 31 | if err != nil { 32 | panic(err) 33 | } 34 | defer conn.Close() 35 | 36 | rows, err := conn.Query(ctx, `select name, time, value from example where name = ? limit 10`, tagName) 37 | if err != nil { 38 | panic(err) 39 | } 40 | defer rows.Close() 41 | 42 | columns, err := rows.Columns() 43 | if err != nil { 44 | panic(err) 45 | } 46 | for _, col := range columns { 47 | fmt.Println(">> column name:", col.Name, "type:", col.DataType) 48 | } 49 | var name string 50 | var ts int64 // can use time.Time, string, int64 51 | var value float64 52 | for rows.Next() { 53 | if err := rows.Scan(&name, &ts, &value); err != nil { 54 | panic(err) 55 | } 56 | fmt.Println(">> name", fmt.Sprintf("%q", name), ", time:", ts, ", value:", value) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /examples/go/cli_queryrow/query_row.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/machbase/neo-server/v8/api" 8 | "github.com/machbase/neo-server/v8/api/machcli" 9 | ) 10 | 11 | const ( 12 | machPort = 5656 13 | machHost = "127.0.0.1" 14 | machUser = "SYS" 15 | machPass = "MANAGER" 16 | 17 | tableName = "example" 18 | tagName = "helloworld" 19 | ) 20 | 21 | func main() { 22 | env, err := machcli.NewDatabase(&machcli.Config{Host: machHost, Port: machPort}) 23 | if err != nil { 24 | panic(err) 25 | } 26 | defer env.Close() 27 | 28 | ctx := context.TODO() 29 | 30 | conn, err := env.Connect(ctx, api.WithPassword(machUser, machPass)) 31 | if err != nil { 32 | panic(err) 33 | } 34 | defer conn.Close() 35 | 36 | row := conn.QueryRow(ctx, `select name, count(*) as c from example group by name having name = ?`, tagName) 37 | if row.Err() != nil { 38 | panic(row.Err()) 39 | } 40 | 41 | var name string 42 | var count int 43 | if err := row.Scan(&name, &count); err != nil { 44 | panic(err) 45 | } 46 | fmt.Println(">> name", fmt.Sprintf("%q", name), ", count(*):", count) 47 | } 48 | -------------------------------------------------------------------------------- /examples/go/grpc_cretable/grpc_cretable.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | // +build ignore 3 | 4 | package main 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "os" 10 | "path/filepath" 11 | 12 | "github.com/machbase/neo-server/v8/api" 13 | "github.com/machbase/neo-server/v8/api/machrpc" 14 | ) 15 | 16 | func main() { 17 | homeDir, err := os.UserHomeDir() 18 | if err != nil { 19 | panic(err) 20 | } 21 | serverAddr := "127.0.0.1:5655" 22 | serverCert := filepath.Join(homeDir, ".config", "machbase", "cert", "machbase_cert.pem") 23 | 24 | // This example substitute server's key & cert for the client's key, cert. 25 | // It is just for the briefness of sample code 26 | // Client applications **SHOULD** issue a certificate for each one. 27 | // Please refer to the "API Authentication" section of the documents. 28 | clientKey := filepath.Join(homeDir, ".config", "machbase", "cert", "machbase_key.pem") 29 | clientCert := filepath.Join(homeDir, ".config", "machbase", "cert", "machbase_cert.pem") 30 | 31 | cli, err := machrpc.NewClient(&machrpc.Config{ 32 | ServerAddr: serverAddr, 33 | Tls: &machrpc.TlsConfig{ 34 | ClientKey: clientKey, 35 | ClientCert: clientCert, 36 | ServerCert: serverCert, 37 | }, 38 | }) 39 | if err != nil { 40 | panic(err) 41 | } 42 | defer cli.Close() 43 | 44 | var ctx = context.TODO() 45 | var conn api.Conn 46 | if c, err := cli.Connect(ctx, api.WithPassword("sys", "manager")); err != nil { 47 | panic(err) 48 | } else { 49 | conn = c 50 | } 51 | defer conn.Close() 52 | 53 | sqlText := `create tag table if not exists example ( 54 | name varchar(100) primary key, 55 | time datetime basetime, 56 | value double 57 | )` 58 | if result := conn.Exec(ctx, sqlText); result.Err() != nil { 59 | panic(result.Err()) 60 | } else { 61 | fmt.Println(result.Message()) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /examples/go/grpc_insert/grpc_insert.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | // +build ignore 3 | 4 | package main 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "os" 10 | "path/filepath" 11 | "time" 12 | 13 | "github.com/machbase/neo-server/v8/api" 14 | "github.com/machbase/neo-server/v8/api/machrpc" 15 | ) 16 | 17 | func main() { 18 | homeDir, err := os.UserHomeDir() 19 | if err != nil { 20 | panic(err) 21 | } 22 | serverAddr := "127.0.0.1:5655" 23 | serverCert := filepath.Join(homeDir, ".config", "machbase", "cert", "machbase_cert.pem") 24 | 25 | // This example substitute server's key & cert for the client's key, cert. 26 | // It is just for the briefness of sample code 27 | // Client applications **SHOULD** issue a certificate for each one. 28 | // Please refer to the "API Authentication" section of the documents. 29 | clientKey := filepath.Join(homeDir, ".config", "machbase", "cert", "machbase_key.pem") 30 | clientCert := filepath.Join(homeDir, ".config", "machbase", "cert", "machbase_cert.pem") 31 | 32 | cli, err := machrpc.NewClient(&machrpc.Config{ 33 | ServerAddr: serverAddr, 34 | Tls: &machrpc.TlsConfig{ 35 | ClientKey: clientKey, 36 | ClientCert: clientCert, 37 | ServerCert: serverCert, 38 | }, 39 | }) 40 | if err != nil { 41 | panic(err) 42 | } 43 | defer cli.Close() 44 | 45 | var ctx = context.TODO() 46 | var conn api.Conn 47 | if c, err := cli.Connect(ctx, api.WithPassword("sys", "manager")); err != nil { 48 | panic(err) 49 | } else { 50 | conn = c 51 | } 52 | defer conn.Close() 53 | 54 | sqlText := `insert into example (name, time, value) values (?, ?, ?)` 55 | if result := conn.Exec(ctx, sqlText, "n1", time.Now(), 1.234); result.Err() != nil { 56 | panic(result.Err()) 57 | } else { 58 | fmt.Println(result.Message()) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /examples/go/grpc_queryrow/grpc_queryrow.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | // +build ignore 3 | 4 | package main 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "os" 10 | "path/filepath" 11 | 12 | "github.com/machbase/neo-server/v8/api" 13 | "github.com/machbase/neo-server/v8/api/machrpc" 14 | ) 15 | 16 | func main() { 17 | homeDir, err := os.UserHomeDir() 18 | if err != nil { 19 | panic(err) 20 | } 21 | serverAddr := "127.0.0.1:5655" 22 | serverCert := filepath.Join(homeDir, ".config", "machbase", "cert", "machbase_cert.pem") 23 | 24 | // This example substitute server's key & cert for the client's key, cert. 25 | // It is just for the briefness of sample code 26 | // Client applications **SHOULD** issue a certificate for each one. 27 | // Please refer to the "API Authentication" section of the documents. 28 | clientKey := filepath.Join(homeDir, ".config", "machbase", "cert", "machbase_key.pem") 29 | clientCert := filepath.Join(homeDir, ".config", "machbase", "cert", "machbase_cert.pem") 30 | 31 | cli, err := machrpc.NewClient(&machrpc.Config{ 32 | ServerAddr: serverAddr, 33 | Tls: &machrpc.TlsConfig{ 34 | ClientKey: clientKey, 35 | ClientCert: clientCert, 36 | ServerCert: serverCert, 37 | }, 38 | }) 39 | if err != nil { 40 | panic(err) 41 | } 42 | defer cli.Close() 43 | 44 | var ctx = context.TODO() 45 | var conn api.Conn 46 | if c, err := cli.Connect(ctx, api.WithPassword("sys", "manager")); err != nil { 47 | panic(err) 48 | } else { 49 | conn = c 50 | } 51 | defer cli.Close() 52 | 53 | var count int 54 | 55 | sqlText := `select count(*) from example` 56 | row := conn.QueryRow(ctx, sqlText) 57 | if err := row.Scan(&count); err != nil { 58 | panic(err) 59 | } 60 | fmt.Println("count=", count) 61 | } 62 | -------------------------------------------------------------------------------- /examples/go/http_get/http_get.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | // +build ignore 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "io" 9 | "net/http" 10 | "net/url" 11 | ) 12 | 13 | func main() { 14 | client := http.Client{} 15 | q := url.QueryEscape("select count(*) from M$SYS_TABLES where name = 'TAGDATA'") 16 | rsp, err := client.Get("http://127.0.0.1:5654/db/query?q=" + q) 17 | if err != nil { 18 | panic(err) 19 | } 20 | 21 | body, err := io.ReadAll(rsp.Body) 22 | if err != nil { 23 | panic(err) 24 | } 25 | 26 | content := string(body) 27 | 28 | if rsp.StatusCode != http.StatusOK { 29 | panic(fmt.Errorf("ERR %s %s", rsp.Status, content)) 30 | } 31 | 32 | fmt.Println(content) 33 | } 34 | -------------------------------------------------------------------------------- /examples/go/http_post_form/http_post_form.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | // +build ignore 3 | 4 | package main 5 | 6 | import ( 7 | "bytes" 8 | "fmt" 9 | "io" 10 | "net/http" 11 | "net/url" 12 | ) 13 | 14 | func main() { 15 | addr := "http://127.0.0.1:5654/db/query" 16 | 17 | data := url.Values{"q": {"select count(*) from M$SYS_TABLES where name = 'TAGDATA'"}} 18 | 19 | client := http.Client{} 20 | rsp, err := client.Post(addr, "application/x-www-form-urlencoded", bytes.NewBufferString(data.Encode())) 21 | if err != nil { 22 | panic(err) 23 | } 24 | 25 | body, err := io.ReadAll(rsp.Body) 26 | if err != nil { 27 | panic(err) 28 | } 29 | 30 | content := string(body) 31 | 32 | if rsp.StatusCode != http.StatusOK { 33 | panic(fmt.Errorf("ERR %s %s", rsp.Status, content)) 34 | } 35 | 36 | fmt.Println(content) 37 | } 38 | -------------------------------------------------------------------------------- /examples/go/http_post_query/http_post_query.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | // +build ignore 3 | 4 | package main 5 | 6 | import ( 7 | "bytes" 8 | "fmt" 9 | "io" 10 | "net/http" 11 | ) 12 | 13 | func main() { 14 | addr := "http://127.0.0.1:5654/db/query" 15 | 16 | queryJson := `{"q":"select count(*) from M$SYS_TABLES where name = 'TAGDATA'"}` 17 | 18 | client := http.Client{} 19 | rsp, err := client.Post(addr, "application/json", bytes.NewBufferString(queryJson)) 20 | if err != nil { 21 | panic(err) 22 | } 23 | 24 | body, err := io.ReadAll(rsp.Body) 25 | if err != nil { 26 | panic(err) 27 | } 28 | 29 | content := string(body) 30 | 31 | if rsp.StatusCode != http.StatusOK { 32 | panic(fmt.Errorf("ERR %s %s", rsp.Status, content)) 33 | } 34 | 35 | fmt.Println(content) 36 | } 37 | -------------------------------------------------------------------------------- /examples/go/http_wave/http_wave.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | // +build ignore 3 | 4 | package main 5 | 6 | import ( 7 | "bytes" 8 | "encoding/json" 9 | "fmt" 10 | "math" 11 | "net/http" 12 | "time" 13 | ) 14 | 15 | type WriteReq struct { 16 | Table string `json:"table"` 17 | Data WriteReqData `json:"data"` 18 | } 19 | 20 | type WriteReqData struct { 21 | Columns []string `json:"columns"` 22 | Rows [][]any `json:"rows"` 23 | } 24 | 25 | func main() { 26 | client := http.Client{} 27 | for ts := range time.Tick(500 * time.Millisecond) { 28 | delta := float64(ts.UnixMilli()%15000) / 15000 29 | theta := 2 * math.Pi * delta 30 | sin, cos := math.Sin(theta), math.Cos(theta) 31 | 32 | content, _ := json.Marshal(&WriteReq{ 33 | Table: "EXAMPLE", 34 | Data: WriteReqData{ 35 | Columns: []string{"name", "time", "value"}, 36 | Rows: [][]any{ 37 | {"wave.sin", ts.UTC().UnixNano(), sin}, 38 | {"wave.cos", ts.UTC().UnixNano(), cos}, 39 | }, 40 | }, 41 | }) 42 | rsp, err := client.Post( 43 | "http://127.0.0.1:5654/db/write/example", 44 | "application/json", 45 | bytes.NewBuffer(content)) 46 | if err != nil { 47 | panic(err) 48 | } 49 | if rsp.StatusCode != http.StatusOK { 50 | panic(fmt.Errorf("response %d", rsp.StatusCode)) 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /examples/go/http_write_csv/http_write_csv.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | // +build ignore 3 | 4 | package main 5 | 6 | import ( 7 | "bytes" 8 | "fmt" 9 | "io" 10 | "net/http" 11 | "strings" 12 | "time" 13 | ) 14 | 15 | type WriteRsp struct { 16 | Success bool `json:"success"` 17 | Reason string `json:"reason"` 18 | Elapse string `json:"elapse"` 19 | Data WriteRspData `json:"data"` 20 | } 21 | 22 | type WriteRspData struct { 23 | AffectedRows uint64 `json:"affectedRows"` 24 | } 25 | 26 | func main() { 27 | addr := "http://127.0.0.1:5654/db/write/TAGDATA" 28 | 29 | rows := []string{ 30 | fmt.Sprintf("my-car,%d,32.1", time.Now().UnixNano()), 31 | fmt.Sprintf("my-car,%d,65.4", time.Now().UnixNano()), 32 | fmt.Sprintf("my-car,%d,76.5", time.Now().UnixNano()), 33 | } 34 | 35 | client := http.Client{} 36 | rsp, err := client.Post(addr, "text/csv", bytes.NewBuffer([]byte(strings.Join(rows, "\n")))) 37 | if err != nil { 38 | panic(err) 39 | } 40 | 41 | body, err := io.ReadAll(rsp.Body) 42 | if err != nil { 43 | panic(err) 44 | } 45 | 46 | content := string(body) 47 | 48 | if rsp.StatusCode != http.StatusOK { 49 | panic(fmt.Errorf("ERR %s %s", rsp.Status, content)) 50 | } 51 | 52 | fmt.Println(content) 53 | } 54 | -------------------------------------------------------------------------------- /examples/go/http_write_json/http_write_json.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | // +build ignore 3 | 4 | package main 5 | 6 | import ( 7 | "bytes" 8 | "encoding/json" 9 | "fmt" 10 | "io" 11 | "net/http" 12 | "time" 13 | ) 14 | 15 | type WriteRsp struct { 16 | Success bool `json:"success"` 17 | Reason string `json:"reason"` 18 | Elapse string `json:"elapse"` 19 | Data WriteRspData `json:"data"` 20 | } 21 | 22 | type WriteRspData struct { 23 | AffectedRows uint64 `json:"affectedRows"` 24 | } 25 | 26 | func main() { 27 | addr := "http://127.0.0.1:5654/db/write/TAGDATA?method=insert" 28 | 29 | rows := [][]any{ 30 | {"my-car", time.Now().UnixNano(), 32.1}, 31 | {"my-car", time.Now().UnixNano(), 65.4}, 32 | {"my-car", time.Now().UnixNano(), 76.5}, 33 | } 34 | columns := []string{"name", "time", "value"} 35 | writeReq := map[string]any{ 36 | "data": map[string]any{ 37 | "columns": columns, 38 | "rows": rows, 39 | }, 40 | } 41 | 42 | queryJson, _ := json.Marshal(&writeReq) 43 | 44 | client := http.Client{} 45 | rsp, err := client.Post(addr, "application/json", bytes.NewBuffer(queryJson)) 46 | if err != nil { 47 | panic(err) 48 | } 49 | 50 | body, err := io.ReadAll(rsp.Body) 51 | if err != nil { 52 | panic(err) 53 | } 54 | 55 | content := string(body) 56 | 57 | if rsp.StatusCode != http.StatusOK { 58 | panic(fmt.Errorf("ERR %s %s", rsp.Status, content)) 59 | } 60 | 61 | fmt.Println(content) 62 | } 63 | -------------------------------------------------------------------------------- /examples/go/nats_pub/nats_pub.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "strings" 7 | "time" 8 | 9 | "github.com/nats-io/nats.go" 10 | ) 11 | 12 | // Before run this program, 13 | // 1. Add bridge in machbase-neo server 14 | // bridge add -t nats my_nats server=127.0.0.1:4222 name=hello 15 | // 2. Add subscriber 16 | // subscriber add hello-nats my_nats test.topic db/write/EXAMPLE:csv; 17 | // 3. Start subscriber 18 | // subscriber start hello-nats 19 | // 4. Run 20 | // go run nats_pub.go -server nats://: -subject hello 21 | func main() { 22 | optServer := flag.String("server", "nats://127.0.0.1:4222", "nats server address") 23 | optSubject := flag.String("subject", "hello", "subject to subscribe") 24 | optRequest := flag.Bool("request", false, "request-response model") 25 | flag.Parse() 26 | 27 | opts := nats.GetDefaultOptions() 28 | opts.Servers = []string{*optServer} 29 | conn, err := opts.Connect() 30 | if err != nil { 31 | panic(err) 32 | } 33 | defer conn.Close() 34 | 35 | tick := time.Now() 36 | lines := []string{} 37 | linesPerMsg := 1 38 | msgCount := 1000000 39 | serial := 0 40 | 41 | for n := 0; n < msgCount; n++ { 42 | for i := 0; i < linesPerMsg; i++ { 43 | line := fmt.Sprintf("hello-nats,%d,1.2345", tick.Add(time.Duration(serial)*time.Microsecond).UnixNano()) 44 | lines = append(lines, line) 45 | serial++ 46 | } 47 | reqData := []byte(strings.Join(lines, "\n")) 48 | lines = lines[0:0] 49 | 50 | if *optRequest { 51 | // A) request-respond model 52 | if rsp, err := conn.Request(*optSubject, reqData, 100*time.Millisecond); err != nil { 53 | panic(err) 54 | } else { 55 | fmt.Println("RESP:", string(rsp.Data)) 56 | } 57 | } else { 58 | // B) fire-and-forget model 59 | if err := conn.Publish(*optSubject, reqData); err != nil { 60 | panic(err) 61 | } 62 | } 63 | } 64 | 65 | fmt.Println("msg sent: ", conn.OutMsgs) 66 | } 67 | -------------------------------------------------------------------------------- /examples/javascript/http_read_csv.js: -------------------------------------------------------------------------------- 1 | 2 | q = "select * from example" 3 | fetch(`http://127.0.0.1:5654/db/query?q=${encodeURIComponent(q)}&format=csv`) 4 | .then(res => { 5 | return res.text(); 6 | }) 7 | .then(data => { 8 | console.log(data) 9 | }) 10 | .catch(err => { 11 | console.log('Fetch Error', err); 12 | }); -------------------------------------------------------------------------------- /examples/javascript/http_read_json.js: -------------------------------------------------------------------------------- 1 | 2 | q = "select * from example" 3 | fetch(`http://127.0.0.1:5654/db/query?q=${encodeURIComponent(q)}`) 4 | .then(res => { 5 | return res.json(); 6 | }) 7 | .then(data => { 8 | console.log(data) 9 | }) 10 | .catch(err => { 11 | console.log('Fetch Error', err); 12 | }); -------------------------------------------------------------------------------- /examples/javascript/http_write_csv.js: -------------------------------------------------------------------------------- 1 | payload = `temperature,1677033057000000000,21.1 2 | humidity,1677033057000000000,0.53` 3 | 4 | fetch('http://127.0.0.1:5654/db/write/example', { 5 | method: 'POST', 6 | headers: { 7 | 'Content-Type':'text/csv' 8 | }, 9 | body: payload 10 | }) 11 | .then(res => { 12 | return res.json(); 13 | }) 14 | .then(data => { 15 | console.log(data) 16 | }) 17 | .catch(err => { 18 | console.log('Fetch Error', err); 19 | }); -------------------------------------------------------------------------------- /examples/javascript/http_write_json.js: -------------------------------------------------------------------------------- 1 | payload = { 2 | data: { 3 | columns: ["NAME", "TIME", "VALUE"], 4 | rows: [ 5 | ['temperature',1677033057000000000,21.1], 6 | ['humidity',1677033057000000000,0.53] 7 | ] 8 | } 9 | } 10 | 11 | fetch('http://127.0.0.1:5654/db/write/example', { 12 | method: 'POST', 13 | headers: { 14 | 'Content-Type':'application/json' 15 | }, 16 | body: JSON.stringify(payload) 17 | }) 18 | .then(res => { 19 | return res.json(); 20 | }) 21 | .then(data => { 22 | console.log(data) 23 | }) 24 | .catch(err => { 25 | console.log('Fetch Error', err); 26 | }); -------------------------------------------------------------------------------- /examples/python/http_read_csv.py: -------------------------------------------------------------------------------- 1 | import requests 2 | params = {"q":"select * from example limit 100", "format":"csv", "heading":"false"} 3 | response = requests.get("http://127.0.0.1:5654/db/query", params) 4 | print(response.text) 5 | -------------------------------------------------------------------------------- /examples/python/http_write_csv.py: -------------------------------------------------------------------------------- 1 | import requests 2 | csvdata = """temperature,1677033057000000000,21.1 3 | humidity,1677033057000000000,0.53 4 | """ 5 | response = requests.post( 6 | "http://127.0.0.1:5654/db/write/example?heading=false", 7 | data=csvdata, 8 | headers={'Content-Type': 'text/csv'}) 9 | print(response.json()) 10 | -------------------------------------------------------------------------------- /mage.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | // +build ignore 3 | 4 | package main 5 | 6 | import ( 7 | "os" 8 | 9 | "github.com/magefile/mage/mage" 10 | ) 11 | 12 | func main() { 13 | os.Exit(mage.Main()) 14 | } 15 | -------------------------------------------------------------------------------- /magefiles/neo-launcher-version.txt: -------------------------------------------------------------------------------- 1 | v0.1.1 -------------------------------------------------------------------------------- /magefiles/neo-web-version.txt: -------------------------------------------------------------------------------- 1 | v8.0.57 -------------------------------------------------------------------------------- /main/machbase-neo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/machbase/neo-server/v8/mods/args" 7 | ) 8 | 9 | func main() { 10 | os.Exit(args.Main()) 11 | } 12 | -------------------------------------------------------------------------------- /main/neoshell/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | shell "github.com/machbase/neo-server/v8/mods/shell" 7 | ) 8 | 9 | func main() { 10 | os.Exit(shell.Main()) 11 | } 12 | -------------------------------------------------------------------------------- /mods/args/help_test.go: -------------------------------------------------------------------------------- 1 | package args 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestXxx(t *testing.T) { 10 | err := doHelp("serve", "") 11 | require.Nil(t, err) 12 | 13 | err = doHelp("shell", "sql") 14 | require.Nil(t, err) 15 | 16 | err = doHelp("timeformat", "") 17 | require.Nil(t, err) 18 | 19 | err = doHelp("tz", "") 20 | require.Nil(t, err) 21 | } 22 | -------------------------------------------------------------------------------- /mods/args/svc_unix.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package args 5 | 6 | import "fmt" 7 | 8 | func doService(_ *Service) { 9 | fmt.Println("command 'service' is only available on Windows") 10 | } 11 | -------------------------------------------------------------------------------- /mods/banner_c.txt: -------------------------------------------------------------------------------- 1 | __ __ _ _ 2 | | \/ | __ _ ___ | |__ | |__ __ _ ___ ___ 3 | | |\/| | / _` | / __| | '_ \ | '_ \ / _` | / __| / _ \ 4 | | | | | | (_| | | (__ | | | | | |_) | | (_| | \__ \ | __/ 5 | |_| |_| \__,_| \___| |_| |_| |_.__/ \__,_| |___/ \___| 6 |  _ __  ___  ___  7 |  | '_ \  / _ \ / _ \  8 |  | | | | | __/ | (_) | 9 |  |_| |_| \___| \___/  10 |     -------------------------------------------------------------------------------- /mods/banner_m.txt: -------------------------------------------------------------------------------- 1 | __ __ _ _ 2 | | \/ | __ _ ___ | |__ | |__ __ _ ___ ___ 3 | | |\/| | / _` | / __| | '_ \ | '_ \ / _` | / __| / _ \ 4 | | | | | | (_| | | (__ | | | | | |_) | | (_| | \__ \ | __/ 5 | |_| |_| \__,_| \___| |_| |_| |_.__/ \__,_| |___/ \___| 6 | _ __ ___ ___ 7 | | '_ \ / _ \ / _ \ 8 | | | | | | __/ | (_) | 9 | |_| |_| \___| \___/ 10 | -------------------------------------------------------------------------------- /mods/bridge/bridge.go: -------------------------------------------------------------------------------- 1 | package bridge 2 | 3 | import ( 4 | bridgerpc "github.com/machbase/neo-server/v8/api/bridge" 5 | "github.com/machbase/neo-server/v8/api/schedule" 6 | "github.com/machbase/neo-server/v8/mods/logging" 7 | "github.com/machbase/neo-server/v8/mods/model" 8 | cmap "github.com/orcaman/concurrent-map/v2" 9 | ) 10 | 11 | func NewService(opts ...Option) Service { 12 | s := &svr{ 13 | log: logging.GetLog("bridge"), 14 | ctxMap: cmap.New[*rowsWrap](), 15 | } 16 | for _, o := range opts { 17 | o(s) 18 | } 19 | return s 20 | } 21 | 22 | type Service interface { 23 | bridgerpc.ManagementServer 24 | bridgerpc.RuntimeServer 25 | 26 | Start() error 27 | Stop() 28 | } 29 | 30 | type Option func(*svr) 31 | 32 | func WithProvider(provider model.BridgeProvider) Option { 33 | return func(s *svr) { 34 | s.models = provider 35 | } 36 | } 37 | 38 | func WithScheduleServer(handler schedule.ManagementServer) Option { 39 | return func(s *svr) { 40 | s.schedMgmtImpl = handler 41 | } 42 | } 43 | 44 | type svr struct { 45 | Service 46 | 47 | log logging.Log 48 | ctxMap cmap.ConcurrentMap[string, *rowsWrap] 49 | 50 | schedMgmtImpl schedule.ManagementServer 51 | models model.BridgeProvider 52 | } 53 | 54 | func (s *svr) Start() error { 55 | lst, err := s.models.LoadAllBridges() 56 | if err != nil { 57 | return err 58 | } 59 | for _, define := range lst { 60 | if err := Register(define); err == nil { 61 | s.log.Infof("add bridge %s type=%s", define.Name, define.Type) 62 | } else { 63 | s.log.Errorf("fail to add bridge %s type=%s, %s", define.Name, define.Type, err.Error()) 64 | } 65 | } 66 | return nil 67 | } 68 | 69 | func (s *svr) Stop() { 70 | UnregisterAll() 71 | s.log.Info("closed.") 72 | } 73 | -------------------------------------------------------------------------------- /mods/bridge/connector/mssql/mssql.go: -------------------------------------------------------------------------------- 1 | package mssql 2 | 3 | import ( 4 | "database/sql" 5 | 6 | _ "github.com/microsoft/go-mssqldb" 7 | ) 8 | 9 | func Connect(dsn string) (*sql.DB, error) { 10 | return sql.Open("sqlserver", dsn) 11 | } 12 | -------------------------------------------------------------------------------- /mods/bridge/connector/mysql/mysql.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "database/sql" 5 | 6 | _ "github.com/go-sql-driver/mysql" 7 | ) 8 | 9 | func Connect(dsn string) (*sql.DB, error) { 10 | return sql.Open("mysql", dsn) 11 | } 12 | -------------------------------------------------------------------------------- /mods/bridge/connector/postgres/postgres.go: -------------------------------------------------------------------------------- 1 | package postgres 2 | 3 | import ( 4 | "database/sql" 5 | 6 | _ "github.com/lib/pq" 7 | ) 8 | 9 | func Connect(dsn string) (*sql.DB, error) { 10 | return sql.Open("postgres", dsn) 11 | } 12 | -------------------------------------------------------------------------------- /mods/bridge/connector/sqlite/sqlite3.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import ( 4 | "database/sql" 5 | 6 | _ "github.com/mattn/go-sqlite3" 7 | ) 8 | 9 | func Connect(dsn string) (*sql.DB, error) { 10 | return sql.Open("sqlite3", dsn) 11 | } 12 | -------------------------------------------------------------------------------- /mods/bridge/internal/postgres/postgres_test.go: -------------------------------------------------------------------------------- 1 | package postgres_test 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "testing" 7 | 8 | embedded_postgres "github.com/fergusstrange/embedded-postgres" 9 | "github.com/machbase/neo-server/v8/api" 10 | "github.com/machbase/neo-server/v8/mods/bridge/internal" 11 | "github.com/machbase/neo-server/v8/mods/bridge/internal/postgres" 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | var newConn func(context.Context) api.Conn 16 | 17 | func TestMain(m *testing.M) { 18 | conf := embedded_postgres.DefaultConfig(). 19 | Username("dbuser"). 20 | Password("dbpass"). 21 | Database("db"). 22 | Port(15454) 23 | pgdb := embedded_postgres.NewDatabase(conf) 24 | if err := pgdb.Start(); err != nil { 25 | panic(err) 26 | } 27 | bridge := postgres.New("pg", "host=127.0.0.1 port=15454 dbname=db user=dbuser password=dbpass sslmode=disable") 28 | bridge.BeforeRegister() 29 | defer bridge.AfterUnregister() 30 | newConn = func(ctx context.Context) api.Conn { 31 | conn, err := bridge.Connect(ctx) 32 | if err != nil { 33 | panic(err) 34 | } 35 | return internal.NewConn(conn) 36 | } 37 | code := m.Run() 38 | if err := pgdb.Stop(); err != nil { 39 | panic(err) 40 | } 41 | os.Exit(code) 42 | } 43 | 44 | func TestPostgres(t *testing.T) { 45 | ctx := context.TODO() 46 | conn := newConn(ctx) 47 | defer conn.Close() 48 | 49 | conn.Exec(ctx, `CREATE TABLE test (id SERIAL PRIMARY KEY, name TEXT)`) 50 | conn.Exec(ctx, `INSERT INTO test (name) VALUES ($1)`, "foo") 51 | conn.Exec(ctx, `INSERT INTO test (name) VALUES ($1)`, "bar") 52 | 53 | rows, err := conn.Query(ctx, `SELECT * FROM test ORDER BY id`) 54 | require.NoError(t, err) 55 | defer rows.Close() 56 | for rows.Next() { 57 | var id int64 58 | var name string 59 | require.NoError(t, rows.Scan(&id, &name)) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /mods/bridge/types.go: -------------------------------------------------------------------------------- 1 | package bridge 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "errors" 7 | ) 8 | 9 | var ErrBridgeDisabled = errors.New("bridge is not enabled") 10 | 11 | type Bridge interface { 12 | Name() string 13 | String() string 14 | 15 | BeforeRegister() error 16 | AfterUnregister() error 17 | } 18 | 19 | type SqlBridge interface { 20 | Bridge 21 | Type() string 22 | DB() *sql.DB 23 | Connect(ctx context.Context) (*sql.Conn, error) 24 | NewScanType(reflectType string, databaseTypeName string) any 25 | NormalizeType(value []any) []any 26 | ParameterMarker(idx int) string 27 | SupportLastInsertId() bool 28 | } 29 | 30 | type PythonBridge interface { 31 | Bridge 32 | Invoke(ctx context.Context, args []string, stdin []byte) (exitCode int, stdout []byte, stderr []byte, err error) 33 | Version(ctx context.Context) (string, error) 34 | } 35 | 36 | type WriteStats struct { 37 | Appended uint64 38 | Inserted uint64 39 | } 40 | 41 | type Subscription interface { 42 | Unsubscribe() error 43 | AddAppended(delta uint64) 44 | AddInserted(delta uint64) 45 | } 46 | -------------------------------------------------------------------------------- /mods/codec/facility/logger.go: -------------------------------------------------------------------------------- 1 | package facility 2 | 3 | import "testing" 4 | 5 | type Logger interface { 6 | Logf(format string, args ...any) 7 | Log(args ...any) 8 | LogDebugf(format string, args ...any) 9 | LogDebug(args ...any) 10 | LogWarnf(format string, args ...any) 11 | LogWarn(args ...any) 12 | LogErrorf(format string, args ...any) 13 | LogError(args ...any) 14 | } 15 | 16 | var DiscardLogger = &discardLogger{} 17 | 18 | type discardLogger struct { 19 | } 20 | 21 | func (l *discardLogger) Logf(format string, args ...any) {} 22 | func (l *discardLogger) Log(args ...any) {} 23 | func (l *discardLogger) LogDebugf(format string, args ...any) {} 24 | func (l *discardLogger) LogDebug(args ...any) {} 25 | func (l *discardLogger) LogWarnf(format string, args ...any) {} 26 | func (l *discardLogger) LogWarn(args ...any) {} 27 | func (l *discardLogger) LogErrorf(format string, args ...any) {} 28 | func (l *discardLogger) LogError(args ...any) {} 29 | 30 | type testLogger struct { 31 | t *testing.T 32 | } 33 | 34 | func TestLogger(t *testing.T) Logger { 35 | return &testLogger{t} 36 | } 37 | func (l *testLogger) Logf(format string, args ...any) { l.t.Helper(); l.t.Logf(format, args...) } 38 | func (l *testLogger) Log(args ...any) { l.t.Helper(); l.t.Log(args...) } 39 | func (l *testLogger) LogDebugf(format string, args ...any) { l.t.Helper(); l.t.Logf(format, args...) } 40 | func (l *testLogger) LogDebug(args ...any) { l.t.Helper(); l.t.Log(args...) } 41 | func (l *testLogger) LogWarnf(format string, args ...any) { l.t.Helper(); l.t.Logf(format, args...) } 42 | func (l *testLogger) LogWarn(args ...any) { l.t.Helper(); l.t.Log(args...) } 43 | func (l *testLogger) LogErrorf(format string, args ...any) { l.t.Helper(); l.t.Logf(format, args...) } 44 | func (l *testLogger) LogError(args ...any) { l.t.Helper(); l.t.Log(args...) } 45 | -------------------------------------------------------------------------------- /mods/codec/facility/volatile.go: -------------------------------------------------------------------------------- 1 | package facility 2 | 3 | import "time" 4 | 5 | type VolatileFileWriter interface { 6 | VolatileFilePrefix() string 7 | VolatileFileWrite(name string, data []byte, deadline time.Time) 8 | } 9 | -------------------------------------------------------------------------------- /mods/codec/internal/base.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | type RowsEncoderBase struct { 4 | httpHeaders map[string][]string 5 | } 6 | 7 | func (reb *RowsEncoderBase) HttpHeaders() map[string][]string { 8 | return reb.httpHeaders 9 | } 10 | 11 | func (reb *RowsEncoderBase) SetHttpHeader(key string, value string) { 12 | if reb.httpHeaders == nil { 13 | reb.httpHeaders = make(map[string][]string) 14 | } 15 | reb.httpHeaders[key] = append(reb.httpHeaders[key], value) 16 | } 17 | 18 | func (reb *RowsEncoderBase) DelHttpHeader(key string) { 19 | if reb.httpHeaders != nil { 20 | return 21 | } 22 | delete(reb.httpHeaders, key) 23 | } 24 | -------------------------------------------------------------------------------- /mods/codec/internal/chart/rendertpls.go: -------------------------------------------------------------------------------- 1 | package chart 2 | 3 | var ChartJsonTemplate = ` 4 | {{- define "chart" }} 5 | { 6 | "chartID":"{{ .ChartID }}", 7 | {{ $len := len .JSAssets }} {{ if gt $len 0 }} 8 | "jsAssets": {{ .JSAssetsNoEscaped }}, 9 | {{ end }} 10 | {{ $len := len .CSSAssets }} {{ if gt $len 0 }} 11 | "cssAssets" : {{ .JSAssetsNoEscaped }}, 12 | {{ end }} 13 | {{ $len := len .JSCodeAssets }} {{ if gt $len 0 }} 14 | "jsCodeAssets": {{ .JSCodeAssetsNoEscaped }}, 15 | {{ end }} 16 | "style": { 17 | "width": "{{ .Width }}", 18 | "height": "{{ .Height }}" 19 | }, 20 | "theme": "{{ .Theme }}" 21 | } 22 | {{ end }} 23 | ` 24 | 25 | var HeaderTemplate = ` 26 | {{ define "header" }} 27 | 28 | 29 | {{ .PageTitle }} 30 | {{- range .JSAssets }} 31 | 32 | {{- end }} 33 | {{- range .CSSAssets }} 34 | 35 | {{- end }} 36 | 40 | 41 | {{ end }} 42 | ` 43 | 44 | var BaseTemplate = ` 45 | {{- define "base" }} 46 |
47 |
48 |
49 | {{- range .JSCodeAssets }} 50 | 51 | {{- end }} 52 | {{ end }} 53 | ` 54 | 55 | var ChartTemplate = `{{- define "chart" }} 56 | 57 | {{- template "header" . }} 58 | 59 | {{- template "base" . }} 60 | 61 | 62 | {{ end }} 63 | ` 64 | -------------------------------------------------------------------------------- /mods/codec/internal/chart/test/anscombe_quartet.json: -------------------------------------------------------------------------------- 1 | { 2 | "chartID": "WejMYXCGcYNL", 3 | "jsAssets": [ 4 | "/web/echarts/echarts.min.js", 5 | "/web/echarts/themes/dark.js" 6 | ], 7 | "style": { 8 | "width": "600px", 9 | "height": "600px" 10 | }, 11 | "theme": "dark" 12 | } -------------------------------------------------------------------------------- /mods/codec/internal/chart/test/compat_bar.js: -------------------------------------------------------------------------------- 1 | (()=>{ 2 | "use strict"; 3 | let _chartID = 'MjYwMjY0NTY1OTY2MTUxNjg_'; 4 | let _chart = echarts.init(document.getElementById(_chartID), "white"); 5 | let _chartOption = { 6 | "legend":{"show":true,"data":["test-data"]}, 7 | "dataZoom":[{"type":"slider", "start":0, "end":100}], 8 | "tooltip":{"show":true, "trigger":"axis"}, 9 | "xAxis":{"name":"time","type":"time"}, 10 | "yAxis":{"name":"demo","type":"value"}, 11 | "series":[ 12 | {"type":"bar", "name":"test-data", "data":[[1692670838086.467,0],[1692670839086.467,1],[1692670840086.467,2]]} 13 | ]}; 14 | _chart.setOption(_chartOption); 15 | _chart.dispatchAction({"areas": {}, "type": ""}); 16 | })(); -------------------------------------------------------------------------------- /mods/codec/internal/chart/test/compat_bar.json: -------------------------------------------------------------------------------- 1 | { 2 | "chartID": "MjYwMjY0NTY1OTY2MTUxNjg_", 3 | "jsAssets": [ 4 | "/web/echarts/echarts.min.js" 5 | ], 6 | "jsCodeAssets": [ 7 | "/web/api/tql-assets/MjYwMjY0NTY1OTY2MTUxNjg_.js" 8 | ], 9 | "style": { 10 | "width": "600px", 11 | "height": "600px" 12 | }, 13 | "theme": "white" 14 | } -------------------------------------------------------------------------------- /mods/codec/internal/chart/test/compat_line.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 |
16 |
17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /mods/codec/internal/chart/test/compat_line.json: -------------------------------------------------------------------------------- 1 | { 2 | "chartID":"WejMYXCGcYNL", 3 | 4 | "jsAssets": ["/web/echarts/echarts.min.js","/web/echarts/themes/westeros.js"], 5 | 6 | 7 | 8 | "jsCodeAssets": ["/web/api/tql-assets/WejMYXCGcYNL.js"], 9 | 10 | "style": { 11 | "width": "400px", 12 | "height": "300px" 13 | }, 14 | "theme": "westeros" 15 | } 16 | -------------------------------------------------------------------------------- /mods/codec/internal/chart/test/compat_line3d.js: -------------------------------------------------------------------------------- 1 | (()=>{ 2 | "use strict"; 3 | let _chartID = 'zmsXewYeZOqW'; 4 | let _chart = echarts.init(document.getElementById(_chartID), "westerose"); 5 | let _chartOption = { 6 | "xAxis3D":{"name":"time","type":"time","show":true}, 7 | "yAxis3D":{"name":"demo","type":"value","show":true}, 8 | "zAxis3D":{"name":"z","type":"value","show":true}, 9 | "grid3D":{"boxWidth":100, "boxHeight":100, "boxDepth":100, "viewControl":{"projection": "orthographic", "autoRotate":false,"speed":0}}, 10 | "title":{"text":"Title", "subtext":"subtitle"}, 11 | "tooltip":{"show":true, "trigger":"axis"}, 12 | "series":[ 13 | {"type":"line3D","coordinateSystem":"cartesian3D","data":[[1692670838086.467,0,0],[1692670839086.467,1,1],[1692670840086.467,2,2]],"shading":"lambert","lineStyle":{"opacity":1,"width":1}} 14 | ]}; 15 | _chart.setOption(_chartOption); 16 | _chart.dispatchAction({"areas": {}, "type": ""}); 17 | })(); -------------------------------------------------------------------------------- /mods/codec/internal/chart/test/compat_line3d.json: -------------------------------------------------------------------------------- 1 | { 2 | "chartID": "zmsXewYeZOqW", 3 | "jsAssets": [ 4 | "/web/echarts/echarts.min.js", 5 | "/web/echarts/echarts-gl.min.js" 6 | ], 7 | "jsCodeAssets": [ 8 | "/web/api/tql-assets/zmsXewYeZOqW.js" 9 | ], 10 | "style": { 11 | "width": "600px", 12 | "height": "600px" 13 | }, 14 | "theme": "westerose" 15 | } -------------------------------------------------------------------------------- /mods/codec/internal/chart/test/compat_scatter.js: -------------------------------------------------------------------------------- 1 | (()=>{ 2 | "use strict"; 3 | let _chartID = 'MjYwMjY0NTY1OTY2MTUxNjg_'; 4 | let _chart = echarts.init(document.getElementById(_chartID), "white"); 5 | let _chartOption = { 6 | "legend":{"show":true,"data":["test-data"]}, 7 | "dataZoom":[{"type":"slider", "start":0, "end":100}], 8 | "tooltip":{"show":true, "trigger":"axis"}, 9 | "xAxis":{"name":"time","type":"time"}, 10 | "yAxis":{"name":"demo","type":"value"}, 11 | "series":[ 12 | {"type":"scatter", "name":"test-data", "data":[[1692670838086.467,0],[1692670839086.467,1],[1692670840086.467,2]]} 13 | ]}; 14 | _chart.setOption(_chartOption); 15 | _chart.dispatchAction({"areas": {}, "type": ""}); 16 | })(); -------------------------------------------------------------------------------- /mods/codec/internal/chart/test/compat_scatter.json: -------------------------------------------------------------------------------- 1 | { 2 | "chartID": "MjYwMjY0NTY1OTY2MTUxNjg_", 3 | "jsAssets": [ 4 | "/web/echarts/echarts.min.js" 5 | ], 6 | "jsCodeAssets": [ 7 | "/web/api/tql-assets/MjYwMjY0NTY1OTY2MTUxNjg_.js" 8 | ], 9 | "style": { 10 | "width": "600px", 11 | "height": "600px" 12 | }, 13 | "theme": "white" 14 | } -------------------------------------------------------------------------------- /mods/codec/internal/chart/test/mark_line.json: -------------------------------------------------------------------------------- 1 | { 2 | "chartID": "WejMYXCGcYNL", 3 | "jsAssets": [ 4 | "/web/echarts/echarts.min.js", 5 | "/web/echarts/themes/dark.js" 6 | ], 7 | "style": { 8 | "width": "600px", 9 | "height": "600px" 10 | }, 11 | "theme": "dark" 12 | } -------------------------------------------------------------------------------- /mods/codec/internal/chart/test/tangential_polar_bar.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 |
16 |
17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /mods/codec/internal/chart/test/tangential_polar_bar.json: -------------------------------------------------------------------------------- 1 | { 2 | "chartID": "WejMYXCGcYNL", 3 | "jsAssets": [ 4 | "/web/echarts/echarts.min.js", 5 | "/web/echarts/themes/dark.js" 6 | ], 7 | "style": { 8 | "width": "600px", 9 | "height": "600px" 10 | }, 11 | "theme": "dark" 12 | } -------------------------------------------------------------------------------- /mods/codec/internal/chart/test/test_candlestick.json: -------------------------------------------------------------------------------- 1 | { 2 | "chartID": "WejMYXCGcYNL", 3 | "jsAssets": [ 4 | "/web/echarts/echarts.min.js", 5 | "/web/echarts/themes/dark.js" 6 | ], 7 | "style": { 8 | "width": "600px", 9 | "height": "600px" 10 | }, 11 | "theme": "dark" 12 | } -------------------------------------------------------------------------------- /mods/codec/internal/chart/test/test_line.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 |
15 |
16 |
17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /mods/codec/internal/chart/test/test_line.js: -------------------------------------------------------------------------------- 1 | (()=>{ 2 | "use strict"; 3 | const _column_0=[1692670838086.467,1692670839086.467,1692670840086.467]; 4 | const _column_1=[0,1,2]; 5 | const _columns=[_column_0,_column_1]; 6 | function column(idx) { return _columns[idx]; } 7 | let _chartID = 'WejMYXCGcYNL'; 8 | let _chart = echarts.init(document.getElementById(_chartID), "white"); 9 | let _chartOption = { 10 | "xAxis": { "type": "time", "data": column(0 ) }, 11 | "yAxis": { "type": "value"}, 12 | "series": [ 13 | { "type": "line", "data": column( 1) } 14 | ] 15 | }; 16 | _chart.setOption(_chartOption); 17 | _chart.dispatchAction({"areas": {}, "type": ""}); 18 | })(); -------------------------------------------------------------------------------- /mods/codec/internal/chart/test/test_line.json: -------------------------------------------------------------------------------- 1 | { 2 | "chartID": "WejMYXCGcYNL", 3 | "jsAssets": [ 4 | "/web/echarts/echarts.min.js" 5 | ], 6 | "jsCodeAssets": ["/web/api/tql-assets/WejMYXCGcYNL.js"], 7 | "style": { 8 | "width": "600px", 9 | "height": "600px" 10 | }, 11 | "theme": "white" 12 | } -------------------------------------------------------------------------------- /mods/codec/internal/chart/test/test_scatter.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 |
15 |
16 |
17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /mods/codec/internal/chart/test/test_scatter.js: -------------------------------------------------------------------------------- 1 | (()=>{ 2 | "use strict"; 3 | const _column_0=[1692670838086.467,1692670839086.467,1692670840086.467]; 4 | const _column_1=[0,1,2]; 5 | const _columns=[_column_0,_column_1]; 6 | function column(idx) { return _columns[idx]; } 7 | let _chartID = 'WejMYXCGcYNL'; 8 | let _chart = echarts.init(document.getElementById(_chartID), "white"); 9 | let _chartOption = { 10 | "xAxis": { "type": "time", "data": column(0) }, 11 | "yAxis": { "type": "value"}, 12 | "series": [ 13 | { "type": "scatter", "data": column(1) } 14 | ] 15 | }; 16 | _chart.setOption(_chartOption); 17 | _chart.dispatchAction({"areas": {}, "type": ""}); 18 | })(); -------------------------------------------------------------------------------- /mods/codec/internal/chart/test/test_scatter.json: -------------------------------------------------------------------------------- 1 | { 2 | "chartID": "WejMYXCGcYNL", 3 | "jsAssets": [ 4 | "/web/echarts/echarts.min.js" 5 | ], 6 | "jsCodeAssets": ["/web/api/tql-assets/WejMYXCGcYNL.js"], 7 | "style": { 8 | "width": "600px", 9 | "height": "600px" 10 | }, 11 | "theme": "white" 12 | } -------------------------------------------------------------------------------- /mods/codec/internal/geomap/rendertpls.go: -------------------------------------------------------------------------------- 1 | package geomap 2 | 3 | var HeaderTemplate = ` 4 | {{ define "header" }} 5 | 6 | 7 | {{ .PageTitle }} 8 | 13 | {{- range .CSSAssets }} 14 | 15 | {{- end }} 16 | {{- range .JSAssets }} 17 | 18 | {{- end }} 19 | 20 | {{ end }} 21 | ` 22 | 23 | var BaseTemplate = ` 24 | {{- define "base" }} 25 |
26 |
27 |
28 | {{- range .JSCodeAssets }} 29 | 30 | {{- end }} 31 | {{ end }} 32 | ` 33 | 34 | var HtmlTemplate = `{{- define "geomap" }} 35 | 36 | {{- template "header" . }} 37 | 38 | {{- template "base" . }} 39 | 43 | 44 | 45 | {{ end }} 46 | ` 47 | 48 | var JsonTemplate = ` 49 | {{- define "geomap" }} 50 | { 51 | "geomapID":"{{ .GeomapID }}", 52 | "style": { 53 | "width": "{{ .Width }}", 54 | "height": "{{ .Height }}", 55 | "grayscale": {{ .TileGrayscale }} 56 | }, 57 | "jsAssets": {{ .JSAssetsNoEscaped }}, 58 | "cssAssets": {{ .CSSAssetsNoEscaped }}, 59 | "jsCodeAssets": {{ .JSCodeAssetsNoEscaped }} 60 | } 61 | {{ end }} 62 | ` 63 | -------------------------------------------------------------------------------- /mods/codec/internal/geomap/test/geomap_test.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "geomapID":"WejMYXCGcYNL", 4 | "style": { 5 | "width": "600px", 6 | "height": "600px", 7 | "grayscale": 0 8 | }, 9 | "jsAssets": ["/web/geomap/leaflet.js"], 10 | "cssAssets": ["/web/geomap/leaflet.css"], 11 | "jsCodeAssets": ["/web/api/tql-assets/WejMYXCGcYNL_opt.js","/web/api/tql-assets/WejMYXCGcYNL.js"] 12 | } 13 | -------------------------------------------------------------------------------- /mods/codec/internal/geomap/test/geomap_test_geojson.json: -------------------------------------------------------------------------------- 1 | { 2 | "geomapID":"WejMYXCGcYNL", 3 | "style": { 4 | "width": "600px", 5 | "height": "600px", 6 | "grayscale": 0 7 | }, 8 | "jsAssets": ["/web/geomap/leaflet.js"], 9 | "cssAssets": ["/web/geomap/leaflet.css"], 10 | "jsCodeAssets": ["/web/api/tql-assets/WejMYXCGcYNL_opt.js","/web/api/tql-assets/WejMYXCGcYNL.js"] 11 | } -------------------------------------------------------------------------------- /mods/codec/internal/html/html.go: -------------------------------------------------------------------------------- 1 | package html 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/machbase/neo-server/v8/mods/codec/internal" 9 | ) 10 | 11 | type Exporter struct { 12 | internal.RowsEncoderBase 13 | imageType string 14 | output io.Writer 15 | } 16 | 17 | func NewEncoder() *Exporter { 18 | return &Exporter{ 19 | imageType: "png", 20 | } 21 | } 22 | 23 | func (ex *Exporter) ContentType() string { 24 | return "application/xhtml+xml" // "text/html" 25 | } 26 | 27 | func (ex *Exporter) Open() error { 28 | return nil 29 | } 30 | 31 | func (ex *Exporter) Close() { 32 | } 33 | 34 | func (ex *Exporter) AddRow(values []any) error { 35 | if len(values) == 2 { 36 | var head string 37 | switch values[0] { 38 | case "image/png": 39 | head = "data:image/png;base64," 40 | case "image/jpeg": 41 | head = "data:image/jpeg;base64," 42 | } 43 | if head != "" { 44 | switch v := values[1].(type) { 45 | case []byte: 46 | enc := base64.NewEncoder(base64.StdEncoding, ex.output) 47 | ex.output.Write([]byte(fmt.Sprintf(`
`)) 50 | default: 51 | return fmt.Errorf("invalid image data type (%T)", v) 52 | } 53 | } 54 | } 55 | return nil 56 | } 57 | 58 | func (ex *Exporter) Flush(heading bool) { 59 | } 60 | 61 | func (ex *Exporter) SetOutputStream(o io.Writer) { 62 | ex.output = o 63 | } 64 | -------------------------------------------------------------------------------- /mods/codec/internal/markdown/test/output_brief.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
column0column1column2
2023/08/22 02:20:38.0860.000000true
.........
23 |
24 |

Total 3 records

25 |
26 |
-------------------------------------------------------------------------------- /mods/codec/internal/markdown/test/output_brief.txt: -------------------------------------------------------------------------------- 1 | |column0|column1|column2| 2 | |:-----|:-----|:-----| 3 | |2023/08/22 02:20:38.086|0.000000|true| 4 | | ... | ... | ... | 5 | 6 | > *Total* 3 *records* -------------------------------------------------------------------------------- /mods/codec/internal/markdown/test/output_md.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
column0column1column2
16926708380864670000.000000true
16926708390864670001.000000false
16926708400864670002.000000true
28 |
-------------------------------------------------------------------------------- /mods/codec/internal/markdown/test/output_md.txt: -------------------------------------------------------------------------------- 1 | |column0|column1|column2| 2 | |:-----|:-----|:-----| 3 | |1692670838086467000|0.000000|true| 4 | |1692670839086467000|1.000000|false| 5 | |1692670840086467000|2.000000|true| 6 | -------------------------------------------------------------------------------- /mods/codec/internal/markdown/test/output_timeformat.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
column0column1column2
2023/08/22 02:20:38.0860.000000true
2023/08/22 02:20:39.0861.000000false
2023/08/22 02:20:40.0862.000000true
28 |
-------------------------------------------------------------------------------- /mods/codec/internal/markdown/test/output_timeformat.txt: -------------------------------------------------------------------------------- 1 | |column0|column1|column2| 2 | |:-----|:-----|:-----| 3 | |2023/08/22 02:20:38.086|0.000000|true| 4 | |2023/08/22 02:20:39.086|1.000000|false| 5 | |2023/08/22 02:20:40.086|2.000000|true| -------------------------------------------------------------------------------- /mods/codec/opts/opts.go: -------------------------------------------------------------------------------- 1 | package opts 2 | 3 | //go:generate go run generate.go 4 | 5 | type Option func(enc any) 6 | 7 | type CanSetHttpHeader interface { 8 | SetHttpHeader(key, value string) 9 | } 10 | 11 | func HttpHeader(k string, v string) Option { 12 | return func(enc any) { 13 | if e, ok := enc.(CanSetHttpHeader); ok { 14 | e.SetHttpHeader(k, v) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /mods/eventbus/message_test.go: -------------------------------------------------------------------------------- 1 | package eventbus_test 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "testing" 7 | 8 | "github.com/machbase/neo-server/v8/mods/eventbus" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestQuestion(t *testing.T) { 13 | bs := &bytes.Buffer{} 14 | enc := json.NewEncoder(bs) 15 | enc.SetIndent("", " ") 16 | 17 | msg := &eventbus.Message{ 18 | Ver: "1.0", 19 | ID: 12345, 20 | Type: "question", 21 | } 22 | err := enc.Encode(msg) 23 | require.NoError(t, err) 24 | require.JSONEq(t, `{ 25 | "ver": "1.0", 26 | "id": 12345, 27 | "type": "question", 28 | "body": null 29 | }`, bs.String()) 30 | bs.Reset() 31 | 32 | msg.Body = &eventbus.BodyUnion{ 33 | OfQuestion: &eventbus.Question{ 34 | Provider: "ollama", 35 | Model: "llm-model", 36 | Text: "Hello, world!", 37 | }, 38 | } 39 | 40 | err = enc.Encode(msg) 41 | require.NoError(t, err) 42 | require.JSONEq(t, `{ 43 | "ver": "1.0", 44 | "id": 12345, 45 | "type": "question", 46 | "body": { 47 | "provider": "ollama", 48 | "model": "llm-model", 49 | "text": "Hello, world!" 50 | } 51 | }`, bs.String()) 52 | 53 | var msg2 eventbus.Message 54 | err = json.Unmarshal(bs.Bytes(), &msg2) 55 | require.NoError(t, err) 56 | require.Equal(t, msg.Ver, msg2.Ver) 57 | require.Equal(t, msg.ID, msg2.ID) 58 | require.Equal(t, msg.Type, msg2.Type) 59 | require.NotNil(t, msg2.Body) 60 | require.NotNil(t, msg2.Body.OfQuestion) 61 | require.Equal(t, msg.Body.OfQuestion.Provider, msg2.Body.OfQuestion.Provider) 62 | require.Equal(t, msg.Body.OfQuestion.Model, msg2.Body.OfQuestion.Model) 63 | require.Equal(t, msg.Body.OfQuestion.Text, msg2.Body.OfQuestion.Text) 64 | } 65 | -------------------------------------------------------------------------------- /mods/jsh/builtin/builtin.go: -------------------------------------------------------------------------------- 1 | package builtin 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "strings" 7 | ) 8 | 9 | //go:generate go run generate.go 10 | 11 | func Code(cmd string) (string, bool) { 12 | cmd = strings.TrimSuffix(cmd, ".js") 13 | src, exists := cmds[cmd] 14 | return src, exists 15 | } 16 | 17 | type JshContext interface { 18 | context.Context 19 | Username() string 20 | Signal() <-chan string 21 | AddCleanup(func(io.Writer)) int64 22 | RemoveCleanup(int64) 23 | } 24 | -------------------------------------------------------------------------------- /mods/jsh/builtin/generate.gen.go: -------------------------------------------------------------------------------- 1 | package builtin 2 | 3 | // Code generated by go generate; DO NOT EDIT. 4 | import ( 5 | _ "embed" 6 | ) 7 | 8 | var cmds = map[string]string{ 9 | "jsh": jshCode, 10 | "kill": killCode, 11 | "ls": lsCode, 12 | "open": openCode, 13 | "ps": psCode, 14 | "servicectl": servicectlCode, 15 | } 16 | 17 | //go:embed jsh.js 18 | var jshCode string 19 | 20 | //go:embed kill.js 21 | var killCode string 22 | 23 | //go:embed ls.js 24 | var lsCode string 25 | 26 | //go:embed open.js 27 | var openCode string 28 | 29 | //go:embed ps.js 30 | var psCode string 31 | 32 | //go:embed servicectl.js 33 | var servicectlCode string 34 | 35 | -------------------------------------------------------------------------------- /mods/jsh/builtin/generate.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | // +build ignore 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | "slices" 10 | "strings" 11 | ) 12 | 13 | func main() { 14 | entries, err := os.ReadDir(".") 15 | if err != nil { 16 | panic(err) 17 | } 18 | 19 | list := make(map[string]string) 20 | 21 | for _, ent := range entries { 22 | if ent.IsDir() || !strings.HasSuffix(ent.Name(), ".js") { 23 | continue 24 | } 25 | cmd := strings.TrimSuffix(ent.Name(), ".js") 26 | name := ent.Name() 27 | list[cmd] = name 28 | } 29 | fd, err := os.OpenFile("generate.gen.go", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) 30 | if err != nil { 31 | panic(err) 32 | } 33 | defer fd.Close() 34 | 35 | fd.WriteString("package builtin\n") 36 | fd.WriteString("\n") 37 | fd.WriteString(`// Code generated by go generate; DO NOT EDIT.` + "\n") 38 | fd.WriteString("import (\n") 39 | fd.WriteString(` _ "embed"` + "\n") 40 | fd.WriteString(")\n") 41 | fd.WriteString("\n") 42 | 43 | names := make([]string, 0, len(list)) 44 | for name := range list { 45 | names = append(names, name) 46 | } 47 | slices.Sort(names) 48 | 49 | fd.WriteString("var cmds = map[string]string{\n") 50 | for _, name := range names { 51 | fd.WriteString(fmt.Sprintf(` "%s": %sCode,`+"\n", name, name)) 52 | } 53 | fd.WriteString("}\n") 54 | fd.WriteString("\n") 55 | 56 | for _, name := range names { 57 | path := list[name] 58 | fd.WriteString(fmt.Sprintf("//go:embed %s\n", path)) 59 | fd.WriteString(fmt.Sprintf("var %sCode string\n", name)) 60 | fd.WriteString("\n") 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /mods/jsh/builtin/jsh.js: -------------------------------------------------------------------------------- 1 | jsh = require("@jsh/process") 2 | jsh.print("Welcome to JSH runtime.\n\n") 3 | jsh.print(" This is an JSH command line runtime in BETA stage.\n") 4 | jsh.print(" The commands and features are subjects to change.\n") 5 | jsh.print(" Type 'exit' to exit the JSH.\n") 6 | jsh.print("\n") 7 | function printResult(r) { 8 | if( r == undefined) { 9 | return 10 | } 11 | if( r.value != undefined) { 12 | jsh.print(r.value, "\n") 13 | } else { 14 | jsh.print(r, "\n") 15 | } 16 | } 17 | 18 | var alive = true; 19 | while(alive) { 20 | jsh.print("jsh", jsh.cwd(), "> ") 21 | line = jsh.readLine() 22 | if(line == undefined || line == "" || line == "\n") { 23 | continue 24 | } 25 | line = line.trim() 26 | parts = jsh.parseCommandLine(line) 27 | for( i = 0; i < parts.length; i++) { 28 | p = parts[i] 29 | args = p.args 30 | if(args[0] == "exit") { 31 | alive = false 32 | break 33 | } else if(args[0] == "cd") { 34 | r = jsh.cd(...args.slice(1)) 35 | printResult(r) 36 | } else if(args[0] == "pwd") { 37 | r = jsh.cwd(...args.slice(1)) 38 | jsh.print(r, "\n") 39 | } else { 40 | try { 41 | r = jsh.exec(args[0], ...args.slice(1)) 42 | printResult(r) 43 | } catch(e) { 44 | jsh.print(e.message, "\n") 45 | } 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /mods/jsh/builtin/kill.js: -------------------------------------------------------------------------------- 1 | jsh = require("@jsh/process") 2 | args = jsh.args() 3 | if( args.length != 2) { 4 | jsh.print("Usage: kill ","\n") 5 | } else { 6 | try { 7 | pid = parseInt(args[1]) 8 | jsh.ps(pid) 9 | if( pid < 1024 ) { 10 | jsh.print("Cannot kill system process\n") 11 | } else { 12 | ret = jsh.kill(pid) 13 | if(ret !== undefined && ret.value != undefined) { 14 | jsh.print(ret.value, "\n") 15 | } 16 | } 17 | } catch(e) { 18 | jsh.print(e.message, "\n") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /mods/jsh/builtin/ls.js: -------------------------------------------------------------------------------- 1 | jsh = require("@jsh/process") 2 | 3 | r = jsh.readDir(".", (ent) => { 4 | fname = ent.name.padEnd(30, " ") 5 | flag = ent.isDir ? (ent.virtual ? "v" : "d") : "-" 6 | flag += ent.readOnly ? "r-" : "rw" 7 | fSize = (""+ent.size).padStart(10, " ") 8 | jsh.print(" ", flag, fSize, " ", fname, "\n") 9 | return true 10 | }) 11 | 12 | if (r !== undefined) { 13 | jsh.print("Error: ", r, "\n") 14 | } -------------------------------------------------------------------------------- /mods/jsh/builtin/open.js: -------------------------------------------------------------------------------- 1 | jsh = require("@jsh/process") 2 | args = jsh.args() 3 | if( args.length != 2) { 4 | jsh.print("Usage: open ","\n") 5 | } else { 6 | try { 7 | file = args[1]; 8 | result = jsh.exists(file); 9 | if( result.isDir) { 10 | jsh.print("Cannot open a directory\n") 11 | } else { 12 | jsh.print("Opening file: ", result.path, "\n") 13 | jsh.openEditor(result.path) 14 | } 15 | } catch(e) { 16 | jsh.print("Error:", e, "\n") 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /mods/jsh/builtin/ps.js: -------------------------------------------------------------------------------- 1 | const {Table, ps} = require("@jsh/process") 2 | 3 | tab = new Table() 4 | tab.appendHeader("PID", "PPID", "User", "Name", "Uptime") 5 | for( const p of ps() ) { 6 | ppid = "-" 7 | if ( p.ppid != 0xFFFFFFFF) { 8 | ppid = p.ppid 9 | } 10 | tab.appendRow(p.pid, ppid, p.user, p.name, p.uptime) 11 | } 12 | tab.render() 13 | -------------------------------------------------------------------------------- /mods/jsh/cleaner.go: -------------------------------------------------------------------------------- 1 | package jsh 2 | 3 | import ( 4 | "io" 5 | "slices" 6 | "sync" 7 | ) 8 | 9 | type Cleaner struct { 10 | cleanups []int64 11 | cleanupTable map[int64]func(io.Writer) 12 | cleanupSeq int64 13 | cleanupMutex sync.Mutex 14 | } 15 | 16 | func (c *Cleaner) AddCleanup(f func(io.Writer)) int64 { 17 | c.cleanupMutex.Lock() 18 | defer c.cleanupMutex.Unlock() 19 | 20 | c.cleanupSeq++ 21 | c.cleanups = append(c.cleanups, c.cleanupSeq) 22 | if c.cleanupTable == nil { 23 | c.cleanupTable = make(map[int64]func(io.Writer)) 24 | } 25 | c.cleanupTable[c.cleanupSeq] = f 26 | return c.cleanupSeq 27 | } 28 | 29 | func (c *Cleaner) RemoveCleanup(tok int64) { 30 | c.cleanupMutex.Lock() 31 | defer c.cleanupMutex.Unlock() 32 | 33 | for i, t := range c.cleanups { 34 | if t == tok { 35 | c.cleanups = append(c.cleanups[:i], c.cleanups[i+1:]...) 36 | delete(c.cleanupTable, tok) 37 | break 38 | } 39 | } 40 | } 41 | 42 | func (c *Cleaner) RunCleanup(w io.Writer) { 43 | c.cleanupMutex.Lock() 44 | defer c.cleanupMutex.Unlock() 45 | 46 | slices.Reverse(c.cleanups) 47 | for _, tok := range c.cleanups { 48 | if f, ok := c.cleanupTable[tok]; ok { 49 | f(w) 50 | } 51 | } 52 | c.cleanups = nil 53 | c.cleanupTable = make(map[int64]func(io.Writer)) 54 | } 55 | -------------------------------------------------------------------------------- /mods/jsh/db/.gitignore: -------------------------------------------------------------------------------- 1 | test/ -------------------------------------------------------------------------------- /mods/jsh/http/test/html/hello.txt: -------------------------------------------------------------------------------- 1 | Hello, Text! -------------------------------------------------------------------------------- /mods/jsh/http/test/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Test HTML

4 | 5 | -------------------------------------------------------------------------------- /mods/jsh/http/test/tmpl/hello.html: -------------------------------------------------------------------------------- 1 | 2 |

Hello, {{.str}}!

3 |

num: {{.num}}

4 |

bool: {{.bool}}

5 | 6 | -------------------------------------------------------------------------------- /mods/jsh/http/test/tmpl/hello_tmpl.html: -------------------------------------------------------------------------------- 1 | 2 |

Hello, {{.str}}!

3 |

num: {{.num}}

4 |

bool: {{.bool}}

5 | 6 | -------------------------------------------------------------------------------- /mods/jsh/mat/symdense_test.go: -------------------------------------------------------------------------------- 1 | package mat_test 2 | 3 | import "testing" 4 | 5 | func TestSymDenseSet(t *testing.T) { 6 | tests := []TestCase{ 7 | { 8 | Name: "dense_set", 9 | Script: `const m = require("@jsh/mat") 10 | n = 5; 11 | a = new m.SymDense(n, null); 12 | count = 1.0; 13 | for(let i = 0; i < n; i++) { 14 | for(let j = i; j < n; j++) { 15 | a.setSym(i, j, count); 16 | count++; 17 | } 18 | } 19 | console.log(m.format(a, { 20 | format: "a = %v\n", prefix: " ", squeeze: true, 21 | })) 22 | 23 | var sub = new m.SymDense(); 24 | sub.subsetSym(a, [0, 2, 4]); 25 | console.log(m.format(sub, { 26 | format: "subset: [0, 2, 4]\n%v\n", squeeze: true, 27 | })) 28 | sub.subsetSym(a, [0, 0, 4]); 29 | console.log(m.format(sub, { 30 | format: "subset: [0, 0, 4]\n%v", squeeze: true, 31 | })) 32 | `, 33 | Expect: []string{ 34 | "a = ⎡1 2 3 4 5⎤", 35 | " ⎢2 6 7 8 9⎥", 36 | " ⎢3 7 10 11 12⎥", 37 | " ⎢4 8 11 13 14⎥", 38 | " ⎣5 9 12 14 15⎦", 39 | "", 40 | "subset: [0, 2, 4]", 41 | "⎡1 3 5⎤", 42 | "⎢3 10 12⎥", 43 | "⎣5 12 15⎦", 44 | "", 45 | "subset: [0, 0, 4]", 46 | "⎡1 1 5⎤", 47 | "⎢1 1 5⎥", 48 | "⎣5 5 15⎦", 49 | "", 50 | }, 51 | }, 52 | } 53 | for _, tc := range tests { 54 | t.Run(tc.Name, func(t *testing.T) { 55 | runTestCase(t, tc) 56 | }) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /mods/jsh/opcua/test_server/README.md: -------------------------------------------------------------------------------- 1 | # OPCUA Simulator Server 2 | 3 | This server provides basic system metrics via the OPCUA protocol. It is intended for testing and simulation purposes. 4 | 5 | ## Exposed OPCUA Nodes 6 | 7 | The following nodes are available and provide simulated metric values: 8 | 9 | - `ns=1;s=sys_cpu` : System CPU usage 10 | - `ns=1;s=sys_mem` : System memory usage 11 | - `ns=1;s=load1` : 1-minute load average 12 | - `ns=1;s=load5` : 5-minute load average 13 | - `ns=1;s=load15` : 15-minute load average 14 | 15 | ## How to Run the OPCUA Simulator Server 16 | 17 | Follow these steps to set up and run the OPCUA simulator server: 18 | 19 | 1. **Create a new directory for the server:** 20 | ```sh 21 | mkdir opcua-server 22 | cd opcua-server 23 | ``` 24 | 25 | 2. **Copy the provided Go source code into a file named `main.go` in this directory.** 26 | 27 | 3. **Initialize a new Go module:** 28 | ```sh 29 | go mod init opcua-server 30 | ``` 31 | 32 | 4. **Download the required dependencies:** 33 | ```sh 34 | go mod tidy 35 | ``` 36 | 37 | 5. **Run the OPCUA simulator server:** 38 | ```sh 39 | go run . 40 | ``` 41 | 42 | The server will start and listen for OPCUA client connections, exposing the nodes listed above. 43 | 44 | ## Connecting to the Server 45 | 46 | You can use any OPCUA client to connect to this server. The endpoint URL will typically be: 47 | ``` 48 | opc.tcp://localhost:4840 49 | ``` 50 | Adjust the address and port as needed if you modify the server code. 51 | 52 | --- 53 | 54 | For further customization or integration, edit `main.go` as needed. 55 | 56 | -------------------------------------------------------------------------------- /mods/jsh/publisher/publisher.go: -------------------------------------------------------------------------------- 1 | package publisher 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | js "github.com/dop251/goja" 8 | "github.com/dop251/goja_nodejs/require" 9 | "github.com/machbase/neo-server/v8/mods/bridge" 10 | ) 11 | 12 | func NewModuleLoader(ctx context.Context) require.ModuleLoader { 13 | return func(rt *js.Runtime, module *js.Object) { 14 | // m = require("@jsh/publisher") 15 | o := module.Get("exports").(*js.Object) 16 | // m.publisher({bridge: "name"}) 17 | o.Set("publisher", func(optObj map[string]any) js.Value { 18 | var cname string 19 | if len(optObj) > 0 { 20 | // parse db options `$.publisher({bridge: "name"})` 21 | if br, ok := optObj["bridge"]; ok { 22 | cname = br.(string) 23 | } 24 | } 25 | br, err := bridge.GetBridge(cname) 26 | if err != nil || br == nil { 27 | return rt.NewGoError(fmt.Errorf("publisher: bridge '%s' not found", cname)) 28 | } 29 | 30 | ret := rt.NewObject() 31 | if mqttC, ok := br.(*bridge.MqttBridge); ok { 32 | ret.Set("publish", func(topic string, payload any) js.Value { 33 | flag, err := mqttC.Publish(topic, payload) 34 | if err != nil { 35 | return rt.NewGoError(fmt.Errorf("publisher: %s", err.Error())) 36 | } 37 | return rt.ToValue(flag) 38 | }) 39 | } else if natsC, ok := br.(*bridge.NatsBridge); ok { 40 | ret.Set("publish", func(subject string, payload any) js.Value { 41 | flag, err := natsC.Publish(subject, payload) 42 | if err != nil { 43 | return rt.NewGoError(fmt.Errorf("publisher: %s", err.Error())) 44 | } 45 | return rt.ToValue(flag) 46 | }) 47 | } else { 48 | return rt.NewGoError(fmt.Errorf("publisher: bridge '%s' not supported", cname)) 49 | } 50 | return ret 51 | }) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /mods/jsh/service_test.go: -------------------------------------------------------------------------------- 1 | package jsh 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/machbase/neo-server/v8/mods/util/ssfs" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestServices(t *testing.T) { 12 | serverFs, _ := ssfs.NewServerSideFileSystem([]string{"/=./test", "/etc/services=./test/etc_services"}) 13 | ssfs.SetDefault(serverFs) 14 | 15 | list, err := ReadServices() 16 | require.NoError(t, err) 17 | 18 | fmt.Println("==>", list.Errors[0].ReadError) 19 | require.Equal(t, "wrong1", list.Errors[0].Name) 20 | require.Equal(t, "json: cannot unmarshal string into Go struct field ServiceConfig.stop_args of type []string", list.Errors[0].ReadError.Error()) 21 | 22 | require.Equal(t, 1, len(list.Added)) 23 | require.NoError(t, list.Added[0].ReadError) 24 | require.Equal(t, "svc1", list.Added[0].Name) 25 | require.Equal(t, "/sbin/svc.js", list.Added[0].StartCmd) 26 | require.Equal(t, []string{"start", "arg1"}, list.Added[0].StartArgs) 27 | 28 | require.Equal(t, "/sbin/svc.js", list.Added[0].StopCmd) 29 | require.Equal(t, []string{"stop"}, list.Added[0].StopArgs) 30 | } 31 | -------------------------------------------------------------------------------- /mods/jsh/test/etc_services/svc1.json: -------------------------------------------------------------------------------- 1 | { 2 | "enabled": true, 3 | "start_cmd": "/sbin/svc.js", 4 | "start_args": ["start", "arg1"], 5 | "stop_cmd": "/sbin/svc.js", 6 | "stop_args": ["stop"] 7 | } -------------------------------------------------------------------------------- /mods/jsh/test/etc_services/wrong1.json: -------------------------------------------------------------------------------- 1 | { 2 | "enabled": true, 3 | "start_cmd": "/sbin/svc.js", 4 | "start_args": ["start", "arg1"], 5 | "stop_cmd": "/sbin/svc.js", 6 | "stop_args": "stop" 7 | } -------------------------------------------------------------------------------- /mods/jsh/test/jsh-cleanup.js: -------------------------------------------------------------------------------- 1 | jsh = require('@jsh/process'); 2 | 3 | c1 = jsh.addCleanup(function () { 4 | console.log("Running cleanup code1..."); 5 | }); 6 | 7 | c2 = jsh.addCleanup(function () { 8 | console.log("Should not run this code2..."); 9 | }); 10 | 11 | c3 = jsh.addCleanup(function () { 12 | console.log("Running cleanup code3..."); 13 | }); 14 | 15 | jsh.removeCleanup(c2); 16 | -------------------------------------------------------------------------------- /mods/jsh/test/jsh-exception.js: -------------------------------------------------------------------------------- 1 | try { 2 | var a = 1; 3 | var b = 2; 4 | var c = a + b; 5 | if (c != 3) { 6 | throw new Error("Test failed: c is not equal to 3"); 7 | } 8 | c.undefinedFunction(); // This will throw an error 9 | } catch (e) { 10 | console.log("Error: " + e); 11 | console.log("Error: " + e.message); 12 | } -------------------------------------------------------------------------------- /mods/jsh/test/jsh-hello-world.js: -------------------------------------------------------------------------------- 1 | console.log("Hello,", "World!"); 2 | 3 | const process = require("@jsh/process"); 4 | process.cd("/etc_services/"); 5 | console.log("Current directory:", process.cwd()); 6 | -------------------------------------------------------------------------------- /mods/jsh/test/jsh-interrupt.js: -------------------------------------------------------------------------------- 1 | 2 | for(i = 0; i < 10000000; i++) { 3 | if (i % 10000 === 0) { 4 | console.log(i); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /mods/logging/module.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "github.com/machbase/neo-server/v8/booter" 5 | ) 6 | 7 | const ModuleId = "machbase.com/neo-logging" 8 | 9 | type Module struct { 10 | } 11 | 12 | func (m *Module) Start() error { 13 | return nil 14 | } 15 | 16 | func (m *Module) Stop() { 17 | } 18 | 19 | func init() { 20 | RegisterBootFactory() 21 | } 22 | 23 | func RegisterBootFactory() { 24 | defaultConf := Config{ 25 | Console: false, 26 | Filename: "-", 27 | Append: true, 28 | RotateSchedule: "@midnight", 29 | MaxSize: 10, 30 | MaxBackups: 1, 31 | MaxAge: 7, 32 | Compress: false, 33 | UTC: false, 34 | DefaultPrefixWidth: 10, 35 | DefaultEnableSourceLocation: false, 36 | DefaultLevel: "TRACE", 37 | } 38 | 39 | booter.Register(ModuleId, 40 | func() *Config { 41 | clone := defaultConf 42 | return &clone 43 | }, 44 | func(conf *Config) (booter.Boot, error) { 45 | Configure(conf) 46 | return &Module{}, nil 47 | }, 48 | ) 49 | } 50 | -------------------------------------------------------------------------------- /mods/model/bridgedef.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type BridgeType string 8 | 9 | const ( 10 | BRIDGE_SQLITE BridgeType = "sqlite" 11 | BRIDGE_POSTGRES BridgeType = "postgres" 12 | BRIDGE_MYSQL BridgeType = "mysql" 13 | BRIDGE_MSSQL BridgeType = "mssql" 14 | BRIDGE_MQTT BridgeType = "mqtt" 15 | BRIDGE_NATS BridgeType = "nats" 16 | ) 17 | 18 | func ParseBridgeType(typ string) (BridgeType, error) { 19 | switch typ { 20 | case "sqlite", "sqlite3": 21 | return BRIDGE_SQLITE, nil 22 | case "postgres", "postgresql": 23 | return BRIDGE_POSTGRES, nil 24 | case "mysql": 25 | return BRIDGE_MYSQL, nil 26 | case "mssql": 27 | return BRIDGE_MSSQL, nil 28 | case "mqtt": 29 | return BRIDGE_MQTT, nil 30 | case "nats": 31 | return BRIDGE_NATS, nil 32 | default: 33 | return "", fmt.Errorf("unsupported bridge type: %s", typ) 34 | } 35 | } 36 | 37 | type BridgeDefinition struct { 38 | Type BridgeType `json:"type"` 39 | Name string `json:"name"` 40 | Path string `json:"path"` 41 | } 42 | 43 | type BridgeProvider interface { 44 | LoadAllBridges() ([]*BridgeDefinition, error) 45 | LoadBridge(name string) (*BridgeDefinition, error) 46 | SaveBridge(def *BridgeDefinition) error 47 | RemoveBridge(name string) error 48 | } 49 | -------------------------------------------------------------------------------- /mods/model/scheduledef.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "strings" 4 | 5 | type ScheduleType string 6 | 7 | const ( 8 | SCHEDULE_UNDEFINED ScheduleType = "" 9 | SCHEDULE_TIMER ScheduleType = "timer" 10 | SCHEDULE_SUBSCRIBER ScheduleType = "subscriber" 11 | ) 12 | 13 | func (typ ScheduleType) String() string { 14 | switch typ { 15 | default: 16 | return "UNDEFINED" 17 | case SCHEDULE_TIMER: 18 | return "TIMER" 19 | case SCHEDULE_SUBSCRIBER: 20 | return "SUBSCRIBER" 21 | } 22 | } 23 | 24 | func ParseScheduleType(typ string) ScheduleType { 25 | switch strings.ToUpper(typ) { 26 | default: 27 | return SCHEDULE_UNDEFINED 28 | case "TIMER": 29 | return SCHEDULE_TIMER 30 | case "SUBSCRIBER": 31 | return SCHEDULE_SUBSCRIBER 32 | } 33 | } 34 | 35 | type ScheduleDefinition struct { 36 | Name string `json:"-"` 37 | Type ScheduleType `json:"type"` 38 | AutoStart bool `json:"autoStart"` 39 | Task string `json:"task"` 40 | 41 | // timer task 42 | Schedule string `json:"schedule,omitempty"` 43 | // subscriber task 44 | Bridge string `json:"bridge,omitempty"` 45 | Topic string `json:"topic,omitempty"` 46 | // mqtt subscriber only 47 | QoS int `json:"qos,omitempty"` 48 | // nats subscriber only 49 | QueueName string `json:"queue,omitempty"` 50 | StreamName string `json:"stream,omitempty"` 51 | } 52 | 53 | type ScheduleProvider interface { 54 | LoadAllSchedules() ([]*ScheduleDefinition, error) 55 | LoadSchedule(name string) (*ScheduleDefinition, error) 56 | SaveSchedule(def *ScheduleDefinition) error 57 | RemoveSchedule(name string) error 58 | UpdateSchedule(def *ScheduleDefinition) error 59 | } 60 | -------------------------------------------------------------------------------- /mods/nums/array.go: -------------------------------------------------------------------------------- 1 | package nums 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func Count(args ...any) (any, error) { 9 | if len(args) == 0 { 10 | return 0.0, nil 11 | } 12 | return float64(len(args)), nil 13 | } 14 | 15 | func Len(args ...any) (any, error) { 16 | if len(args) == 0 { 17 | return 0.0, nil 18 | } 19 | if arr, ok := args[0].([][]any); ok { 20 | return float64(len(arr)), nil 21 | } else if arr, ok := args[0].([]any); ok { 22 | return float64(len(arr)), nil 23 | } else if arr, ok := args[0].([]string); ok { 24 | return float64(len(arr)), nil 25 | } else if str, ok := args[0].(string); ok { 26 | return float64(len(str)), nil 27 | } else { 28 | return float64(len(args)), nil 29 | } 30 | } 31 | 32 | func Element(args ...any) (any, error) { 33 | if len(args) < 3 { 34 | return nil, fmt.Errorf("f(element) invalud number of args (n:%d)", len(args)) 35 | } 36 | var idx int 37 | var idxArg = args[len(args)-1] 38 | if n, ok := idxArg.(float64); ok { 39 | idx = int(n) 40 | } else { 41 | if n, ok := idxArg.(int); ok { 42 | idx = n 43 | } else { 44 | return nil, fmt.Errorf("f(element) index of element should be int, but %T", idxArg) 45 | } 46 | } 47 | if len(args)-1 <= idx { 48 | return nil, fmt.Errorf("f(element) out of index %d / %d", idx, len(args)-1) 49 | } 50 | switch v := args[idx].(type) { 51 | case int: 52 | return float64(v), nil 53 | case int64: 54 | return float64(v), nil 55 | case float64: 56 | return v, nil 57 | case string: 58 | return v, nil 59 | case bool: 60 | return v, nil 61 | case time.Time: 62 | return float64(v.UnixNano()), nil 63 | case *time.Time: 64 | return float64(v.UnixNano()), nil 65 | default: 66 | return nil, fmt.Errorf("f(element) unsupported type %T", v) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /mods/nums/fakegen.go: -------------------------------------------------------------------------------- 1 | package nums 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type FakeGenerator struct { 8 | C <-chan FakeVal 9 | ch chan FakeVal 10 | functor FakeFunctor 11 | samplingRate int 12 | ticker *time.Ticker 13 | 14 | batchThreshold time.Duration 15 | } 16 | 17 | type FakeFunctor func(t time.Time) float64 18 | 19 | func NewFakeGenerator(s FakeFunctor, samplingRate int) *FakeGenerator { 20 | gs := &FakeGenerator{ 21 | functor: s, 22 | samplingRate: samplingRate, 23 | batchThreshold: 5 * time.Microsecond, 24 | } 25 | gs.ch = make(chan FakeVal, 100) 26 | gs.C = gs.ch 27 | 28 | go gs.run() 29 | return gs 30 | } 31 | 32 | func (gs *FakeGenerator) run() { 33 | T := int(1*time.Second) / gs.samplingRate 34 | if time.Duration(T) >= gs.batchThreshold { 35 | gs.ticker = time.NewTicker(time.Duration(T)) 36 | for t := range gs.ticker.C { 37 | y := gs.functor(t) 38 | gs.ch <- FakeVal{T: t.UnixNano(), V: y} 39 | } 40 | } else { 41 | samplesPerTick := 1 42 | tick := LCM(int64(T), int64(gs.batchThreshold)) 43 | gs.ticker = time.NewTicker(time.Duration(tick)) 44 | samplesPerTick = int(tick / int64(T)) 45 | // fmt.Println("samplingRate ", gs.samplingRate) 46 | // fmt.Println("T ", time.Duration(T).String()) 47 | // fmt.Println("tick ", time.Duration(tick).String()) 48 | // fmt.Println("samplesPerTick", samplesPerTick) 49 | for t := range gs.ticker.C { 50 | for i := 0; i < samplesPerTick; i++ { 51 | t = t.Add(time.Duration(i * T)) 52 | y := gs.functor(t) 53 | gs.ch <- FakeVal{T: t.UnixNano(), V: y} 54 | } 55 | } 56 | } 57 | } 58 | 59 | func (gs *FakeGenerator) Stop() { 60 | if gs.ticker != nil { 61 | gs.ticker.Stop() 62 | } 63 | 64 | if gs.ch != nil { 65 | close(gs.ch) 66 | } 67 | } 68 | 69 | type FakeVal struct { 70 | // unix nano seconds 71 | T int64 72 | // value in float64 73 | V float64 74 | } 75 | -------------------------------------------------------------------------------- /mods/nums/fft/fft.go: -------------------------------------------------------------------------------- 1 | package fft 2 | 3 | import ( 4 | "math/cmplx" 5 | "time" 6 | 7 | "gonum.org/v1/gonum/dsp/fourier" 8 | ) 9 | 10 | func FastFourierTransform(times []time.Time, values []float64) ([]float64, []float64) { 11 | lenSamples := len(times) 12 | samplesDuration := times[lenSamples-1].Sub(times[0]) 13 | period := float64(lenSamples) / (float64(samplesDuration) / float64(time.Second)) 14 | fft := fourier.NewFFT(lenSamples) 15 | amplifier := func(v float64) float64 { 16 | return v * 2.0 / float64(lenSamples) 17 | } 18 | coeff := fft.Coefficients(nil, values) 19 | retHz := []float64{} 20 | retAmpl := []float64{} 21 | for i, c := range coeff { 22 | hz := fft.Freq(i) * period 23 | if hz == 0 { 24 | continue 25 | } 26 | // if hz < minHz { 27 | // continue 28 | // } 29 | // if hz > maxHz { 30 | // continue 31 | // } 32 | magnitude := cmplx.Abs(c) 33 | amplitude := amplifier(magnitude) 34 | // phase = cmplx.Phase(c) 35 | retHz = append(retHz, hz) 36 | retAmpl = append(retAmpl, amplitude) 37 | } 38 | return retHz, retAmpl 39 | } 40 | -------------------------------------------------------------------------------- /mods/nums/geometry.go: -------------------------------------------------------------------------------- 1 | package nums 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | ) 7 | 8 | // DegreesToRadians converts from degrees to radians. 9 | func DegreesToRadians(d float64) float64 { 10 | return d * math.Pi / 180 11 | } 12 | 13 | // A Point is a Lng/Lat 2d point. 14 | type Point [2]float64 15 | 16 | func (p Point) X() float64 { return p[0] } 17 | func (p Point) Y() float64 { return p[1] } 18 | func (p Point) Lat() float64 { return p[1] } 19 | func (p Point) Lon() float64 { return p[0] } 20 | func (p Point) Dimensions() int { return 0 } 21 | 22 | // Equal returns if two points are equal. 23 | func (p Point) Equal(o Point) bool { 24 | return p[0] == o[0] && p[1] == o[1] 25 | } 26 | 27 | func (p Point) String() string { 28 | return fmt.Sprintf("[%v,%v]", p[0], p[1]) 29 | } 30 | 31 | type Line struct { 32 | Start Point 33 | End Point 34 | } 35 | 36 | func (l Line) Dimensions() int { return 1 } 37 | 38 | // Cartesian distance 39 | func (l Line) DistanceTo(p Point) float64 { 40 | a, b, c := l.Coefficients() 41 | return math.Abs(a*p.X()+b*p.Y()+c) / math.Sqrt(a*a+b*b) 42 | } 43 | 44 | // returns the three coefficients that define a line 45 | // A line can be defined by following equation. 46 | // 47 | // ax + by + c = 0 48 | func (l Line) Coefficients() (a, b, c float64) { 49 | a = l.Start.Y() - l.End.Y() 50 | b = l.End.X() - l.Start.X() 51 | c = l.Start.X()*l.End.Y() - l.End.X()*l.Start.Y() 52 | return a, b, c 53 | } 54 | 55 | func (l Line) SeekMostDistant(points []Point) (idx int, maxDist float64) { 56 | for i, p := range points { 57 | d := l.DistanceTo(p) 58 | if d > maxDist { 59 | maxDist = d 60 | idx = i 61 | } 62 | } 63 | return 64 | } 65 | -------------------------------------------------------------------------------- /mods/nums/histogram_test.go: -------------------------------------------------------------------------------- 1 | package nums_test 2 | 3 | import ( 4 | "encoding/json" 5 | "math" 6 | "math/rand" 7 | "testing" 8 | 9 | "github.com/machbase/neo-server/v8/mods/nums" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func mapToJSON(m map[string]any) string { 14 | ret, _ := json.Marshal(m) 15 | return string(ret) 16 | } 17 | 18 | type H map[string]interface{} 19 | 20 | func TestHistogram(t *testing.T) { 21 | h := nums.Histogram{MaxBins: 100} 22 | require.JSONEq(t, mapToJSON(H{"p50": 0.000000, "p90": 0.000000, "p99": 0.000000}), h.String()) 23 | h.Add(1) 24 | require.JSONEq(t, mapToJSON(H{"p50": 1.000000, "p90": 1.000000, "p99": 1.000000}), h.String()) 25 | for i := 2; i < 100; i++ { 26 | h.Add(float64(i)) 27 | } 28 | require.JSONEq(t, mapToJSON(H{"p50": 50.000000, "p90": 90.000000, "p99": 99.000000}), h.String()) 29 | h.Reset() 30 | require.JSONEq(t, mapToJSON(H{"p50": 0.000000, "p90": 0.000000, "p99": 0.000000}), h.String()) 31 | } 32 | 33 | func TestHistogramNormalDist(t *testing.T) { 34 | hist := nums.Histogram{MaxBins: 100} 35 | for i := 0; i < 10000; i++ { 36 | hist.Add(rand.Float64() * 10) 37 | } 38 | 39 | if v := hist.Quantile(0.5); math.Abs(v.Value()-5) > 0.5 { 40 | t.Fatalf("expected 5, got %f", v) 41 | } 42 | 43 | if v := hist.Quantile(0.9); math.Abs(v.Value()-9) > 0.5 { 44 | t.Fatalf("expected 9, got %f", v) 45 | } 46 | 47 | if v := hist.Quantile(0.99); math.Abs(v.Value()-10) > 0.5 { 48 | t.Fatalf("expected 10, got %f", v) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /mods/nums/kalman/CREDIT.TXT: -------------------------------------------------------------------------------- 1 | 2 | https://github.com/rosshemsley/kalman/ 3 | -------------------------------------------------------------------------------- /mods/nums/kalman/kalman_test.go: -------------------------------------------------------------------------------- 1 | package kalman 2 | 3 | import ( 4 | "math" 5 | "testing" 6 | "time" 7 | 8 | "github.com/machbase/neo-server/v8/mods/nums/kalman/models" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestKalman(t *testing.T) { 13 | var ts time.Time 14 | values := []float64{1.3, 10.2, 5.0, 3.4} 15 | 16 | model := models.NewSimpleModel(ts, values[0], models.SimpleModelConfig{ 17 | InitialVariance: 1.0, 18 | ProcessVariance: 1.0, 19 | ObservationVariance: 2.0, 20 | }) 21 | filter := NewKalmanFilter(model) 22 | 23 | var result []float64 24 | for _, v := range values { 25 | ts = ts.Add(time.Second) 26 | filter.Update(ts, model.NewMeasurement(v)) 27 | result = append(result, math.Round(model.Value(filter.State())*100000)/100000) 28 | } 29 | expect := []float64{1.3, 5.75, 5.375, 4.3875} 30 | require.EqualValues(t, expect, result) 31 | } 32 | 33 | func TestKalmanSmoother(t *testing.T) { 34 | var ts time.Time 35 | values := []float64{1.3, 10.2, 5.0, 3.4} 36 | 37 | model := models.NewSimpleModel(ts, values[0], models.SimpleModelConfig{ 38 | InitialVariance: 1.0, 39 | ProcessVariance: 1.0, 40 | ObservationVariance: 2.0, 41 | }) 42 | smoother := NewKalmanSmoother(model) 43 | 44 | var result []float64 45 | for _, v := range values { 46 | ts = ts.Add(time.Second) 47 | states, _ := smoother.Smooth(NewMeasurementAtTime(ts, model.NewMeasurement(v))) 48 | result = append(result, math.Round(model.Value(states[0].State)*100000)/100000) 49 | } 50 | expect := []float64{1.3, 6.64, 3.76667, 2.8} 51 | require.EqualValues(t, expect, result) 52 | } 53 | -------------------------------------------------------------------------------- /mods/nums/kalman/models/model.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "time" 5 | 6 | "gonum.org/v1/gonum/mat" 7 | ) 8 | 9 | type State struct { 10 | Time time.Time 11 | State mat.Vector 12 | Covariance mat.Matrix 13 | } 14 | 15 | type Measurement struct { 16 | Covariance mat.Matrix 17 | Value mat.Vector 18 | ObservationModel mat.Matrix 19 | } 20 | 21 | // LinearModel is used to initialize hidden states in the model and 22 | // provide transition matrices to the filter. 23 | // kalman/models provides commonly used models. 24 | type LinearModel interface { 25 | InitialState() State 26 | Transition(dt time.Duration) mat.Matrix 27 | CovarianceTransition(dt time.Duration) mat.Matrix 28 | } 29 | -------------------------------------------------------------------------------- /mods/nums/kalman/models/simple.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "time" 5 | 6 | "gonum.org/v1/gonum/mat" 7 | ) 8 | 9 | type SimpleModelConfig struct { 10 | InitialVariance float64 11 | ProcessVariance float64 12 | ObservationVariance float64 13 | } 14 | 15 | // SimpleModel provides the most basic Kalman Filter example of modelling a Brownian time series 16 | // in a single dimension. This is just a wrapper around the BrownianModel, with a simplified interface 17 | // that operates directly on floating point values rather than on vectors. 18 | type SimpleModel struct { 19 | model *BrownianModel 20 | } 21 | 22 | func NewSimpleModel(initialTime time.Time, initialValue float64, cfg SimpleModelConfig) *SimpleModel { 23 | return &SimpleModel{ 24 | model: NewBrownianModel( 25 | initialTime, 26 | mat.NewVecDense(1, []float64{initialValue}), 27 | BrownianModelConfig{ 28 | InitialVariance: cfg.InitialVariance, 29 | ProcessVariance: cfg.ProcessVariance, 30 | ObservationVariance: cfg.ObservationVariance, 31 | }, 32 | ), 33 | } 34 | } 35 | 36 | func (s *SimpleModel) InitialState() State { 37 | return s.model.initialState 38 | } 39 | 40 | func (s *SimpleModel) Transition(dt time.Duration) mat.Matrix { 41 | return s.model.Transition(dt) 42 | } 43 | 44 | func (s *SimpleModel) CovarianceTransition(dt time.Duration) mat.Matrix { 45 | return s.model.CovarianceTransition(dt) 46 | } 47 | 48 | func (s *SimpleModel) NewMeasurement(value float64) *Measurement { 49 | return s.model.NewMeasurement(mat.NewVecDense(1, []float64{value})) 50 | } 51 | 52 | // Value is a helper to extract the current value from the Kalman hidden state 53 | func (*SimpleModel) Value(state mat.Vector) float64 { 54 | return state.AtVec(0) 55 | } 56 | -------------------------------------------------------------------------------- /mods/nums/mercatortiles.go: -------------------------------------------------------------------------------- 1 | package nums 2 | 3 | import "math" 4 | 5 | func Tile2LatLon(X, Y, Z int) (lat float64, lon float64) { 6 | n := math.Pi - 2.0*math.Pi*float64(Y)/math.Exp2(float64(Z)) 7 | lat = 180.0 / math.Pi * math.Atan(0.5*(math.Exp(n)-math.Exp(-n))) 8 | lon = float64(X)/math.Exp2(float64(Z))*360.0 - 180.0 9 | return lat, lon 10 | } 11 | 12 | func LatLon2Tile(lat, lon float64, z int) (x, y int) { 13 | n := math.Exp2(float64(z)) 14 | x = int(math.Floor((lon + 180.0) / 360.0 * n)) 15 | if float64(x) >= n { 16 | x = int(n - 1) 17 | } 18 | y = int(math.Floor((1.0 - math.Log(math.Tan(lat*math.Pi/180.0)+1.0/math.Cos(lat*math.Pi/180.0))/math.Pi) / 2.0 * n)) 19 | return 20 | } 21 | 22 | // calculates the resolution (meters/pixel) for given zoom level 23 | func TileResolution(zoom int) float64 { 24 | initialResolution := 2 * math.Pi * 6378137 / TileSize 25 | return initialResolution / math.Pow(2, float64(zoom)) 26 | } 27 | 28 | // gives the zoom level for given resolution (measured at Equator) 29 | func TileZoom(resolution float64) int { 30 | var round = func(a float64) float64 { 31 | if a < 0 { 32 | return math.Ceil(a - 0.5) 33 | } 34 | return math.Floor(a + 0.5) 35 | } 36 | 37 | initialResolution := 2 * math.Pi * 6378137 / TileSize 38 | zoom := round(math.Log(initialResolution/resolution) / math.Log(2)) 39 | return int(zoom) 40 | } 41 | -------------------------------------------------------------------------------- /mods/nums/mercatortiles_test.go: -------------------------------------------------------------------------------- 1 | package nums 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/wroge/wgs84" 8 | ) 9 | 10 | func TestLatLonTileCoord(t *testing.T) { 11 | // https://tile.openstreetmap.org/17/111812/50783.png 12 | zoom, tileX, tileY := 17, 111812, 50783 13 | fmt.Printf("tileX:%d tileY:%d zoom:%d\n", tileX, tileY, zoom) 14 | fmt.Printf("https://tile.openstreetmap.org/%d/%d/%d.png\n\n", zoom, tileX, tileY) 15 | 16 | // 20553782 / 50784 17 | lat, lon := Tile2LatLon(tileX, tileY, zoom) 18 | //east, north := FromPixels(float64(tileX*256), float64(tileY*256), zoom) 19 | fmt.Printf("lon:%f lat:%f %f,%f\n", lon, lat, lat, lon) 20 | 21 | tileX, tileY = LatLon2Tile(lat, lon, zoom) 22 | fmt.Printf("https://tile.openstreetmap.org/%d/%d/%d.png\n\n", zoom, tileX, tileY) 23 | // fmt.Printf("pixelX:%.f pixelY:%.f\n", pixelX, pixelY) 24 | 25 | east, north := PixelsToMeters(float64(tileX), float64(tileY), zoom) 26 | fmt.Printf("east1 :%.f north1:%.f\n", east, north) 27 | 28 | east, north, _ = wgs84.LonLat().To(wgs84.WebMercator())(lon, lat, 0) 29 | fmt.Printf("east2 :%.f north2:%.f\n", east, north) 30 | 31 | tileX, tileY = MetersToTile(east, north, zoom) 32 | fmt.Printf("tileX:%d tileY:%d zoom:%d\n", tileX, tileY, zoom) 33 | } 34 | -------------------------------------------------------------------------------- /mods/nums/opensimplex/generator_test.go: -------------------------------------------------------------------------------- 1 | package opensimplex 2 | 3 | import ( 4 | "compress/gzip" 5 | "encoding/json" 6 | "io" 7 | "math" 8 | "os" 9 | "path" 10 | "testing" 11 | "time" 12 | ) 13 | 14 | func loadSamples() <-chan []float64 { 15 | c := make(chan []float64) 16 | go func() { 17 | f, err := os.Open(path.Join("test", "samples.json.gz")) 18 | if err != nil { 19 | panic(err.Error()) 20 | } 21 | defer f.Close() 22 | 23 | gz, err := gzip.NewReader(f) 24 | if err != nil { 25 | panic(err.Error()) 26 | } 27 | 28 | dec := json.NewDecoder(gz) 29 | for { 30 | var sample []float64 31 | if err := dec.Decode(&sample); err == io.EOF { 32 | break 33 | } else if err != nil { 34 | panic(err.Error()) 35 | } else { 36 | c <- sample 37 | } 38 | } 39 | close(c) 40 | }() 41 | 42 | return c 43 | } 44 | 45 | func TestSamples(t *testing.T) { 46 | samples := loadSamples() 47 | n := New(0) 48 | 49 | for s := range samples { 50 | var expected, actual float64 51 | switch len(s) { 52 | case 3: 53 | expected = s[2] 54 | actual = n.Eval(s[0], s[1]) 55 | case 4: 56 | expected = s[3] 57 | actual = n.Eval(s[0], s[1], s[2]) 58 | case 5: 59 | expected = s[4] 60 | actual = n.Eval(s[0], s[1], s[2], s[3]) 61 | default: 62 | t.Fatalf("Unexpected size sample: %d", len(s)) 63 | } 64 | 65 | tolerant := math.Pow(0.1, 12) 66 | if expected-actual > tolerant { 67 | t.Fatalf("Expected %v, got %v for %dD sample at %v", 68 | expected, actual, len(s)-1, s[:len(s)-1]) 69 | } 70 | } 71 | } 72 | 73 | func TestTimeDomain(t *testing.T) { 74 | n := New(0) 75 | 76 | for i := 0; i < 10; i++ { 77 | v := n.Eval(float64(time.Now().Nanosecond())) 78 | t.Logf("==>%v", v) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /mods/nums/opensimplex/test/samples.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/machbase/neo-server/93d7c126c0438af5f830eeba6e963de91da9185b/mods/nums/opensimplex/test/samples.json.gz -------------------------------------------------------------------------------- /mods/nums/oscillator/oscillator.go: -------------------------------------------------------------------------------- 1 | package oscillator 2 | 3 | import ( 4 | "math" 5 | "time" 6 | 7 | "github.com/machbase/neo-server/v8/mods/nums/opensimplex" 8 | ) 9 | 10 | type Generator struct { 11 | Amplitude float64 12 | Frequency float64 13 | Phase float64 14 | Bias float64 15 | Functor func(float64) float64 16 | } 17 | 18 | func New(frequency float64, amplitude float64) *Generator { 19 | return &Generator{ 20 | Amplitude: amplitude, 21 | Frequency: frequency, 22 | Functor: math.Sin, 23 | } 24 | } 25 | func (g *Generator) Eval(x float64) float64 { 26 | if g.Functor == nil { 27 | g.Functor = math.Sin 28 | } 29 | return g.Amplitude*g.Functor(2*math.Pi*g.Frequency*x+g.Phase) + g.Bias 30 | } 31 | 32 | func (g *Generator) EvalTime(t time.Time) float64 { 33 | x := float64(t.UnixNano()) / float64(time.Second) 34 | return g.Eval(x) 35 | } 36 | 37 | type Composite interface { 38 | EvalTime(t time.Time) float64 39 | } 40 | 41 | func NewComposite(s []*Generator) Composite { 42 | return &composite{ 43 | sigs: s, 44 | } 45 | } 46 | 47 | func NewCompositeWithNoise(s []*Generator, noiseMaxAmplitude float64) Composite { 48 | var noise *opensimplex.Generator 49 | if noiseMaxAmplitude != 0 { 50 | noise = opensimplex.New(time.Now().UnixNano()) 51 | } 52 | return &composite{ 53 | sigs: s, 54 | noise: noise, 55 | } 56 | } 57 | 58 | type composite struct { 59 | sigs []*Generator 60 | noise *opensimplex.Generator 61 | 62 | noiseMaxAmplitude float64 63 | } 64 | 65 | func (c *composite) EvalTime(t time.Time) float64 { 66 | y := 0.0 67 | for _, s := range c.sigs { 68 | y += s.EvalTime(t) 69 | } 70 | if c.noise != nil { 71 | y += c.noiseMaxAmplitude + c.noise.Eval(float64(t.Nanosecond())) 72 | } 73 | return y 74 | } 75 | -------------------------------------------------------------------------------- /mods/nums/simplify.go: -------------------------------------------------------------------------------- 1 | package nums 2 | 3 | // Ram-Douglas-Peucker simplify 4 | func SimplifyPath(points []Point, ep float64) []Point { 5 | if len(points) <= 2 { 6 | return points 7 | } 8 | 9 | l := Line{Start: points[0], End: points[len(points)-1]} 10 | 11 | idx, maxDist := l.SeekMostDistant(points) 12 | if maxDist >= ep { 13 | left := SimplifyPath(points[:idx+1], ep) 14 | right := SimplifyPath(points[idx:], ep) 15 | return append(left[:len(left)-1], right...) 16 | } 17 | 18 | return []Point{points[0], points[len(points)-1]} 19 | } 20 | -------------------------------------------------------------------------------- /mods/nums/simplify_test.go: -------------------------------------------------------------------------------- 1 | package nums 2 | 3 | import "testing" 4 | 5 | func TestSimplifyPath(t *testing.T) { 6 | points := []Point{ 7 | {0, 0}, 8 | {1, 2}, 9 | {2, 7}, 10 | {3, 1}, 11 | {4, 8}, 12 | {5, 2}, 13 | {6, 8}, 14 | {7, 3}, 15 | {8, 3}, 16 | {9, 0}, 17 | } 18 | 19 | t.Run("Threshold=0", func(t *testing.T) { 20 | if len(SimplifyPath(points, 0)) != 10 { 21 | t.Error("simplified path should have all points") 22 | } 23 | }) 24 | 25 | t.Run("Threshold=2", func(t *testing.T) { 26 | if len(SimplifyPath(points, 2)) != 7 { 27 | t.Error("simplified path should only have 7 points") 28 | } 29 | }) 30 | 31 | t.Run("Threshold=5", func(t *testing.T) { 32 | if len(SimplifyPath(points, 100)) != 2 { 33 | t.Error("simplified path should only have two points") 34 | } 35 | }) 36 | } 37 | 38 | func TestSeekMostDistantPoint(t *testing.T) { 39 | l := Line{Start: Point{0, 0}, End: Point{0, 10}} 40 | points := []Point{ 41 | {13, 13}, 42 | {1, 15}, 43 | {1, 1}, 44 | {3, 6}, 45 | } 46 | 47 | idx, maxDist := l.SeekMostDistant(points) 48 | 49 | if idx != 0 { 50 | t.Errorf("failed to find most distant point away from a line, got %d", idx) 51 | } 52 | 53 | if maxDist != 13 { 54 | t.Errorf("maximum distance is incorrect, got %f", maxDist) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /mods/scheduler/sched_subs_test.go: -------------------------------------------------------------------------------- 1 | package scheduler 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestInsertPayload(t *testing.T) { 10 | tt := []struct { 11 | name string 12 | payload string 13 | expect []string 14 | }{ 15 | { 16 | name: "array_of_array", 17 | payload: `{"data":{"columns":["one","two","three"], "rows":[[1,2,3],[4,5,6],[7,8,9]]}}`, 18 | expect: []string{"one", "two", "three"}, 19 | }, 20 | } 21 | 22 | for _, tc := range tt { 23 | t.Run(tc.name, func(t *testing.T) { 24 | result := extractColumns([]byte(tc.payload)) 25 | require.EqualValues(t, tc.expect, result) 26 | }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /mods/server/assets/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/machbase/neo-server/93d7c126c0438af5f830eeba6e963de91da9185b/mods/server/assets/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /mods/server/assets/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/machbase/neo-server/93d7c126c0438af5f830eeba6e963de91da9185b/mods/server/assets/apple-touch-icon.png -------------------------------------------------------------------------------- /mods/server/assets/echarts/themes/index.js: -------------------------------------------------------------------------------- 1 | define(["require", "exports"], function (require, exports) { 2 | "use strict"; 3 | Object.defineProperty(exports, "__esModule", { value: true }); 4 | var version = '1.0.0'; 5 | function load_ipython_extension() { 6 | console.log("echarts-themes-js " + version + " has been loaded"); 7 | } 8 | exports.load_ipython_extension = load_ipython_extension; 9 | }); 10 | -------------------------------------------------------------------------------- /mods/server/assets/echarts/themes/vintage.js: -------------------------------------------------------------------------------- 1 | !function(e,o){"function"==typeof define&&define.amd?define(["exports","echarts"],o):"object"==typeof exports&&"string"!=typeof exports.nodeName?o(exports,require("echarts")):o({},e.echarts)}(this,function(e,o){var r;if(!o)return r="ECharts is not Loaded",void("undefined"!=typeof console&&console&&console.error&&console.error(r));var t=["#d87c7c","#919e8b","#d7ab82","#6e7074","#61a0a8","#efa18d","#787464","#cc7e63","#724e58","#4b565b"];o.registerTheme("vintage",{color:t,backgroundColor:"#fef8ef",graph:{color:t}})}); -------------------------------------------------------------------------------- /mods/server/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/machbase/neo-server/93d7c126c0438af5f830eeba6e963de91da9185b/mods/server/assets/favicon.ico -------------------------------------------------------------------------------- /mods/server/assets/geomap/images/layers-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/machbase/neo-server/93d7c126c0438af5f830eeba6e963de91da9185b/mods/server/assets/geomap/images/layers-2x.png -------------------------------------------------------------------------------- /mods/server/assets/geomap/images/layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/machbase/neo-server/93d7c126c0438af5f830eeba6e963de91da9185b/mods/server/assets/geomap/images/layers.png -------------------------------------------------------------------------------- /mods/server/assets/geomap/images/marker-icon-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/machbase/neo-server/93d7c126c0438af5f830eeba6e963de91da9185b/mods/server/assets/geomap/images/marker-icon-2x.png -------------------------------------------------------------------------------- /mods/server/assets/geomap/images/marker-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/machbase/neo-server/93d7c126c0438af5f830eeba6e963de91da9185b/mods/server/assets/geomap/images/marker-icon.png -------------------------------------------------------------------------------- /mods/server/assets/geomap/images/marker-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/machbase/neo-server/93d7c126c0438af5f830eeba6e963de91da9185b/mods/server/assets/geomap/images/marker-shadow.png -------------------------------------------------------------------------------- /mods/server/svrmgmt_test.go: -------------------------------------------------------------------------------- 1 | package server_test 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "testing" 7 | 8 | "github.com/machbase/neo-server/v8/mods/server" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestClientTokenRSA(t *testing.T) { 13 | rc, err := rsa.GenerateKey(rand.Reader, 4096) 14 | require.Nil(t, err) 15 | 16 | token, err := server.GenerateClientToken("abcdefg", rc, "b") 17 | require.Nil(t, err) 18 | require.True(t, len(token) > 0) 19 | // t.Logf("Token: %s", token) 20 | 21 | pass, err := server.VerifyClientToken(token, &rc.PublicKey) 22 | require.Nil(t, err) 23 | require.True(t, pass) 24 | 25 | pass, err = server.VerifyClientToken(token+"wrong", rc) 26 | require.NotNil(t, err) 27 | require.False(t, pass) 28 | } 29 | 30 | func TestClientTokenECDSA(t *testing.T) { 31 | ec := server.NewEllipticCurveP521() 32 | pri, pub, err := ec.GenerateKeys() 33 | require.Nil(t, err) 34 | require.NotNil(t, pri) 35 | require.NotNil(t, pub) 36 | 37 | token, err := server.GenerateClientToken("abcdefg", pri, "b") 38 | require.Nil(t, err) 39 | require.True(t, len(token) > 0) 40 | t.Logf("Token: %s", token) 41 | 42 | pass, err := server.VerifyClientToken(token, &pri.PublicKey) 43 | require.Nil(t, err) 44 | require.True(t, pass) 45 | 46 | pass, err = server.VerifyClientToken(token+"wrong", pri) 47 | require.NotNil(t, err) 48 | require.False(t, pass) 49 | } 50 | -------------------------------------------------------------------------------- /mods/server/test/csv_append.tql: -------------------------------------------------------------------------------- 1 | CSV(payload()) 2 | MAPVALUE(1, parseTime(value(1), "ns")) 3 | MAPVALUE(2, parseFloat(value(2))) 4 | APPEND( table("example") ) 5 | -------------------------------------------------------------------------------- /mods/server/test/csv_map.tql: -------------------------------------------------------------------------------- 1 | CSV(payload()) 2 | MAPVALUE(1,value(1)+"0") 3 | CSV() 4 | -------------------------------------------------------------------------------- /mods/server/test/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/machbase/neo-server/93d7c126c0438af5f830eeba6e963de91da9185b/mods/server/test/image.png -------------------------------------------------------------------------------- /mods/server/test/test_markdown_list.md: -------------------------------------------------------------------------------- 1 | ## markdown test 2 | - file_root {{ file_root }} 3 | - file_path {{ file_path }} 4 | - file_name {{ file_name }} 5 | - file_dir {{ file_dir }} 6 | -------------------------------------------------------------------------------- /mods/server/test/test_markdown_list.txt: -------------------------------------------------------------------------------- 1 |

markdown test

2 |
    3 |
  • file_root /web/api/tql
  • 4 |
  • file_path /web/api/tql/sample/file.wrk
  • 5 |
  • file_name file.wrk
  • 6 |
  • file_dir /web/api/tql/sample
  • 7 |
8 |
-------------------------------------------------------------------------------- /mods/server/test/test_markdown_list_utf8.md: -------------------------------------------------------------------------------- 1 | ## markdown test 2 | - file_root {{ file_root }} 3 | - file_path {{ file_path }} 4 | - file_name {{ file_name }} 5 | - file_dir {{ file_dir }} 6 | -------------------------------------------------------------------------------- /mods/server/test/test_markdown_list_utf8.txt: -------------------------------------------------------------------------------- 1 |

markdown test

2 |
    3 |
  • file_root /web/api/tql
  • 4 |
  • file_path /web/api/tql/语言/文檔.wrk
  • 5 |
  • file_name 文檔.wrk
  • 6 |
  • file_dir /web/api/tql/语言
  • 7 |
8 |
-------------------------------------------------------------------------------- /mods/server/test/test_markdown_mermaid.md: -------------------------------------------------------------------------------- 1 | # Flowchart 2 | ## Node shapes 3 | 4 | ```mermaid 5 | flowchart LR 6 | id1("(Text box)") --> id2(["[Text box]"]) 7 | id2 --> id3[["[[subroutine]]"]] 8 | id3 --> id4[("[(database)]")] 9 | id4 --> id5(("((circle))")) 10 | 11 | id6>">asymmetric]"] --> id7{"{rhombus}"} 12 | id7 --> id8{{"{{hexagon}}"}} 13 | id8 --> id9[/"[/paralleogram/]"/] 14 | 15 | idA[\"[\parallegram alt\]"\] --> idB[/"[/Trapezoid\]"\] 16 | idB -->idC[\"[\Trapezoid alt/]"/] 17 | idC -->idD((("(((double circle)))"))) 18 | ``` -------------------------------------------------------------------------------- /mods/server/test/test_markdown_mermaid.txt: -------------------------------------------------------------------------------- 1 |

Flowchart

2 |

Node shapes

3 |
flowchart LR
 4 |   id1("(Text box)") --> id2(["[Text box]"]) 
 5 |   id2 --> id3[["[[subroutine]]"]]
 6 |   id3 --> id4[("[(database)]")]
 7 |   id4 --> id5(("((circle))"))
 8 | 
 9 |   id6>">asymmetric]"] --> id7{"{rhombus}"}
10 |   id7 --> id8{{"{{hexagon}}"}}
11 |   id8 --> id9[/"[/paralleogram/]"/]
12 | 
13 |   idA[\"[\parallegram alt\]"\] --> idB[/"[/Trapezoid\]"\]
14 |   idB -->idC[\"[\Trapezoid alt/]"/]
15 |   idC -->idD((("(((double circle)))")))
16 | 
-------------------------------------------------------------------------------- /mods/server/web/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/machbase/neo-server/93d7c126c0438af5f830eeba6e963de91da9185b/mods/server/web/.gitkeep -------------------------------------------------------------------------------- /mods/shell/internal/action/complete.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/nyaosorg/go-box/v2" 7 | "github.com/nyaosorg/go-readline-ny" 8 | ) 9 | 10 | type AutoComplete struct { 11 | PrefixCompleterInterface 12 | } 13 | 14 | var _ readline.Command = (*AutoComplete)(nil) 15 | 16 | func (c *AutoComplete) String() string { 17 | return "COMPLETION_SHELL" 18 | } 19 | 20 | func (C *AutoComplete) Call(ctx context.Context, B *readline.Buffer) readline.Result { 21 | line := []rune(B.String()) 22 | newLines, length := C.Do(line, B.Cursor) 23 | list := []string{} 24 | for _, line := range newLines { 25 | list = append(list, string(line)) 26 | } 27 | if len(list) == 1 { 28 | str := list[0] 29 | B.InsertAndRepaint(str) 30 | } else if len(list) > 0 { 31 | prefix := "" 32 | if len(line) >= length { 33 | prefix = string(line[len(line)-length:]) 34 | } 35 | fullnames := []string{} 36 | for i := range list { 37 | fullnames = append(fullnames, prefix+list[i]) 38 | } 39 | B.Out.WriteByte('\n') 40 | selected := box.Choose(fullnames, B.Out) 41 | B.Out.WriteByte('\n') 42 | B.RepaintLastLine() 43 | if selected >= 0 { 44 | B.InsertAndRepaint(list[selected]) 45 | } 46 | } 47 | return readline.CONTINUE 48 | } 49 | -------------------------------------------------------------------------------- /mods/shell/internal/action/history.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "strings" 7 | 8 | "github.com/nyaosorg/go-readline-ny" 9 | ) 10 | 11 | var _ readline.IHistory = (*History)(nil) 12 | 13 | type History struct { 14 | buffer []string 15 | limit int 16 | filepath string 17 | } 18 | 19 | func NewHistory(limit int) *History { 20 | ret := &History{ 21 | limit: limit, 22 | buffer: make([]string, 0), 23 | } 24 | // TODO 25 | ret.filepath = filepath.Join(PrefDir(), ".neoshell_history") 26 | bytes, err := os.ReadFile(ret.filepath) 27 | if err != nil { 28 | return ret 29 | } 30 | ret.buffer = strings.Split(string(bytes), "\n") 31 | ret.trimLimit() 32 | return ret 33 | } 34 | 35 | func (h *History) Len() int { 36 | return len(h.buffer) 37 | } 38 | 39 | func (h *History) At(at int) string { 40 | return h.buffer[at] 41 | } 42 | 43 | func (h *History) Add(line string) { 44 | if len(h.buffer) > 0 && h.buffer[len(h.buffer)-1] == line { 45 | return 46 | } 47 | h.buffer = append(h.buffer, line) 48 | h.trimLimit() 49 | } 50 | 51 | func (h *History) trimLimit() { 52 | if len(h.buffer) > h.limit { 53 | h.buffer = h.buffer[len(h.buffer)-h.limit:] 54 | } 55 | h.flush() 56 | } 57 | 58 | func (h *History) flush() { 59 | os.WriteFile(h.filepath, []byte(strings.Join(h.buffer, "\n")), 0644) 60 | } 61 | -------------------------------------------------------------------------------- /mods/shell/internal/action/parser.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | "time" 7 | 8 | "github.com/alecthomas/kong" 9 | ) 10 | 11 | type TimezoneParser struct { 12 | } 13 | 14 | // implements kong.TypeMapper 15 | func (tp *TimezoneParser) Decode(ctx *kong.DecodeContext, target reflect.Value) error { 16 | token, err := ctx.Scan.PopValue("tz") 17 | if err != nil { 18 | return err 19 | } 20 | tz := token.String() 21 | if strings.ToLower(tz) == "local" { 22 | target.Set(reflect.ValueOf(time.Local)) 23 | return nil 24 | } else if tz == "UTC" { 25 | target.Set(reflect.ValueOf(time.UTC)) 26 | return nil 27 | } 28 | if tz, err := time.LoadLocation(tz); err != nil { 29 | return err 30 | } else { 31 | target.Set(reflect.ValueOf(tz)) 32 | } 33 | return nil 34 | } 35 | 36 | func KongOptions(helpFunc func() error) []kong.Option { 37 | return []kong.Option{ 38 | kong.HelpOptions{Compact: true}, 39 | kong.Exit(func(int) {}), 40 | kong.TypeMapper(reflect.TypeOf((*time.Location)(nil)), &TimezoneParser{}), 41 | kong.Help(func(options kong.HelpOptions, ctx *kong.Context) error { return helpFunc() }), 42 | } 43 | } 44 | 45 | func Kong(grammar any, helpFunc func() error) (*kong.Kong, error) { 46 | return kong.New(grammar, KongOptions(helpFunc)...) 47 | } 48 | -------------------------------------------------------------------------------- /mods/shell/internal/action/registry.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "strings" 7 | ) 8 | 9 | var sqlCommands = []string{ 10 | "select", "insert", "update", "delete", "alter", 11 | "create", "drop", "truncate", "exec", 12 | "mount", "unmount", "backup", 13 | "grant", "revoke", 14 | } 15 | 16 | var globalCommands = make(map[string]*Cmd) 17 | var globalHelps = make(map[string]*CmdSpec) 18 | 19 | func RegisterCmd(cmd *Cmd) error { 20 | name := strings.ToLower(cmd.Name) 21 | for _, c := range sqlCommands { 22 | if name == c { 23 | return fmt.Errorf("command %q can not be override", name) 24 | } 25 | } 26 | globalCommands[name] = cmd 27 | RegisterHelp(name, cmd.Spec) 28 | return nil 29 | } 30 | 31 | func RegisterHelp(name string, spec *CmdSpec) { 32 | globalHelps[name] = spec 33 | } 34 | 35 | func IsSqlCommand(cmd string) bool { 36 | cmd = strings.ToLower(cmd) 37 | for _, c := range sqlCommands { 38 | if c == cmd { 39 | return true 40 | } 41 | } 42 | return false 43 | } 44 | 45 | func FindCmd(name string) *Cmd { 46 | return globalCommands[name] 47 | } 48 | 49 | func Commands() []*Cmd { 50 | list := []*Cmd{} 51 | for _, v := range globalCommands { 52 | list = append(list, v) 53 | } 54 | sort.Slice(list, func(i, j int) bool { 55 | return list[i].Name <= list[j].Name 56 | }) 57 | return list 58 | } 59 | 60 | func BuildPrefixCompleter() PrefixCompleterInterface { 61 | commands := []*Cmd{} 62 | for _, cmd := range globalCommands { 63 | commands = append(commands, cmd) 64 | } 65 | sort.Slice(commands, func(i, j int) bool { 66 | return commands[i].Name < commands[j].Name 67 | }) 68 | pc := make([]PrefixCompleterInterface, 0) 69 | for _, cmd := range commands { 70 | if cmd.PcFunc != nil { 71 | pc = append(pc, cmd.PcFunc()) 72 | } 73 | } 74 | return NewPrefixCompleter(pc...) 75 | } 76 | 77 | func AppendHistory(text string) { 78 | 79 | } 80 | -------------------------------------------------------------------------------- /mods/shell/internal/cmd/connect.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/machbase/neo-server/v8/mods/shell/internal/action" 7 | "github.com/machbase/neo-server/v8/mods/util" 8 | ) 9 | 10 | func init() { 11 | action.RegisterCmd(&action.Cmd{ 12 | Name: "connect", 13 | PcFunc: pcConnect, 14 | Action: doConnect, 15 | Desc: "Reconnect to another user", 16 | Usage: strings.ReplaceAll(helpConnect, "\t", " "), 17 | }) 18 | } 19 | 20 | const helpConnect = ` connect /` 21 | 22 | func pcConnect() action.PrefixCompleterInterface { 23 | return action.PcItem("connect") 24 | } 25 | 26 | type ConnectCmd struct { 27 | Identifier string `arg:"" name:"username/password"` 28 | Help bool `kong:"-"` 29 | } 30 | 31 | func doConnect(ctx *action.ActionContext) { 32 | cmd := &ConnectCmd{} 33 | parser, err := action.Kong(cmd, func() error { ctx.Println(helpConnect); cmd.Help = true; return nil }) 34 | if err != nil { 35 | ctx.Println("ERR", err.Error()) 36 | return 37 | } 38 | _, err = parser.Parse(util.SplitFields(ctx.Line, false)) 39 | if cmd.Help { 40 | return 41 | } 42 | if err != nil { 43 | ctx.Println("ERR", err.Error()) 44 | return 45 | } 46 | var username string 47 | var password string 48 | if toks := strings.SplitN(cmd.Identifier, "/", 2); len(toks) == 2 { 49 | username = toks[0] 50 | password = toks[1] 51 | } else { 52 | ctx.Println("ERR", "no username/password is specified") 53 | return 54 | } 55 | 56 | ok, reason, err := ctx.Actor.Reconnect(username, password) 57 | if err != nil { 58 | ctx.Println("ERR", err.Error()) 59 | return 60 | } else if !ok { 61 | ctx.Println("ERR", reason) 62 | return 63 | } 64 | ctx.Println("Connected successfully.") 65 | } 66 | -------------------------------------------------------------------------------- /mods/shell/internal/cmd/ping.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/machbase/neo-server/v8/mods/shell/internal/action" 8 | "github.com/machbase/neo-server/v8/mods/util" 9 | ) 10 | 11 | func init() { 12 | action.RegisterCmd(&action.Cmd{ 13 | Name: "ping", 14 | PcFunc: pcPing, 15 | Action: doPing, 16 | Desc: "Test connection to server", 17 | Usage: helpPing, 18 | }) 19 | } 20 | 21 | const helpPing string = ` ping Test connection to server` 22 | 23 | // const helpPing string = ` ping [options] Test connection to server 24 | // options: 25 | // -n,--count repeat count (default:1 ) 26 | // ` 27 | 28 | type PingCmd struct { 29 | Repeat int `name:"repeat" short:"n" default:"1"` 30 | Help bool `kong:"-"` 31 | } 32 | 33 | func pcPing() action.PrefixCompleterInterface { 34 | return action.PcItem("ping") 35 | } 36 | 37 | func doPing(ctx *action.ActionContext) { 38 | cmd := &PingCmd{} 39 | parser, err := action.Kong(cmd, func() error { fmt.Println(helpPing); cmd.Help = true; return nil }) 40 | if err != nil { 41 | fmt.Println("ERR", err.Error()) 42 | return 43 | } 44 | _, err = parser.Parse(util.SplitFields(ctx.Line, false)) 45 | if cmd.Help { 46 | return 47 | } 48 | if err != nil { 49 | fmt.Println("ERR", err.Error()) 50 | return 51 | } 52 | 53 | for i := 0; i < cmd.Repeat && ctx.Ctx.Err() == nil; i++ { 54 | if i != 0 { 55 | time.Sleep(time.Second) 56 | } 57 | latency, err := ctx.Actor.Database().Ping(ctx.Ctx) 58 | if err != nil { 59 | fmt.Println("ping", err.Error()) 60 | } else { 61 | fmt.Printf("seq=%d time=%s\n", i, latency) 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /mods/shell/internal/cmd/set.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/machbase/neo-server/v8/mods/shell/internal/action" 8 | "github.com/machbase/neo-server/v8/mods/util" 9 | ) 10 | 11 | func init() { 12 | lines := []string{} 13 | if pref, err := action.LoadPref(); err == nil { 14 | for _, itm := range pref.Items() { 15 | lines = append(lines, fmt.Sprintf(" set %-10s %s", itm.Name, itm.Description())) 16 | } 17 | } 18 | 19 | action.RegisterCmd(&action.Cmd{ 20 | Name: "set", 21 | PcFunc: pcSet, 22 | Action: doSet, 23 | Desc: "Settings of the shell", 24 | Usage: fmt.Sprintf(" set \n%s\n", strings.Join(lines, "\n")), 25 | ClientAction: true, 26 | }) 27 | } 28 | 29 | func pcSet() action.PrefixCompleterInterface { 30 | top := action.PcItem("set") 31 | if pref, err := action.LoadPref(); err == nil { 32 | for _, itm := range pref.Items() { 33 | pc := action.PcItem(itm.Name) 34 | for _, en := range itm.Enum { 35 | ec := action.PcItem(en) 36 | pc.Children = append(pc.Children, ec) 37 | } 38 | top.Children = append(top.Children, pc) 39 | } 40 | } 41 | return top 42 | } 43 | 44 | func doSet(ctx *action.ActionContext) { 45 | args := util.SplitFields(ctx.Line, true) 46 | pref := ctx.Pref() 47 | if len(args) == 0 { 48 | box := ctx.NewBox([]string{"NAME", "VALUE", "DESCRIPTION"}) 49 | itms := pref.Items() 50 | for _, itm := range itms { 51 | box.AppendRow(itm.Name, itm.Value(), itm.Description()) 52 | } 53 | box.Render() 54 | return 55 | } 56 | 57 | if len(args) == 2 { 58 | itm := pref.Item(strings.ToLower(args[0])) 59 | if itm == nil { 60 | ctx.Printf("unknown set key %q\n", args[0]) 61 | } else { 62 | value := util.StripQuote(args[1]) 63 | if err := itm.SetValue(value); err != nil { 64 | ctx.Println("ERR", err.Error()) 65 | } else { 66 | pref.Save() 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /mods/shell/internal/cmd/shutdown.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | client "github.com/machbase/neo-server/v8/mods/shell/internal/action" 5 | ) 6 | 7 | func init() { 8 | client.RegisterCmd(&client.Cmd{ 9 | Name: "shutdown", 10 | PcFunc: pcShutdown, 11 | Action: doShutdown, 12 | Desc: "Shutdown server process", 13 | Usage: helpShutdown, 14 | }) 15 | } 16 | 17 | const helpShutdown string = ` shutdown stop the server process 18 | ` 19 | 20 | type ShutdownCmd struct { 21 | Interactive bool `kong:"-"` 22 | Help bool `kong:"-"` 23 | } 24 | 25 | func pcShutdown() client.PrefixCompleterInterface { 26 | return client.PcItem("shutdown") 27 | } 28 | 29 | func doShutdown(ctx *client.ActionContext) { 30 | f := ctx.ShutdownServerFunc() 31 | if f == nil { 32 | ctx.Println("ERR", "server shutdown is not allowed") 33 | } else { 34 | err := f() 35 | if err != nil { 36 | ctx.Println("ERR", err.Error()) 37 | return 38 | } 39 | ctx.Println("server shutting down...") 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /mods/tql/fm_stat_test.go: -------------------------------------------------------------------------------- 1 | package tql 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestHistogramOrder(t *testing.T) { 10 | hist := &HistogramPredictedBins{} 11 | 12 | hist.buckets = map[StatCategoryName]*HistogramBuckets{} 13 | hist.buckets["Cat.A"] = nil 14 | hist.buckets["Cat.B"] = nil 15 | hist.buckets["Cat.C"] = nil 16 | hist.buckets["Cat.D"] = nil 17 | 18 | result := hist.orderedCategoryNames() 19 | require.EqualValues(t, []StatCategoryName{"Cat.A", "Cat.B", "Cat.C", "Cat.D"}, result) 20 | 21 | hist.orders = []StatCategoryName{"Cat.D", "Cat.C", "Cat.B", "Cat.A"} 22 | result = hist.orderedCategoryNames() 23 | require.EqualValues(t, []StatCategoryName{"Cat.D", "Cat.C", "Cat.B", "Cat.A"}, result) 24 | 25 | hist.orders = []StatCategoryName{"Cat.D", "Cat.C"} 26 | result = hist.orderedCategoryNames() 27 | require.EqualValues(t, []StatCategoryName{"Cat.D", "Cat.C", "Cat.A", "Cat.B"}, result) 28 | 29 | hist.orders = []StatCategoryName{"Cat.D"} 30 | result = hist.orderedCategoryNames() 31 | require.EqualValues(t, []StatCategoryName{"Cat.D", "Cat.A", "Cat.B", "Cat.C"}, result) 32 | } 33 | -------------------------------------------------------------------------------- /mods/tql/internal/expression/lexer.go: -------------------------------------------------------------------------------- 1 | package expression 2 | 3 | type lexerStream struct { 4 | source []rune 5 | position int 6 | length int 7 | } 8 | 9 | func newLexerStream(source string) *lexerStream { 10 | ret := &lexerStream{} 11 | ret.source = []rune(source) 12 | ret.length = len(ret.source) 13 | return ret 14 | } 15 | 16 | func (ls *lexerStream) readCharacter() rune { 17 | r := ls.source[ls.position] 18 | ls.position++ 19 | return r 20 | } 21 | 22 | func (ls *lexerStream) rewind(amount int) { 23 | ls.position -= amount 24 | if ls.position < 0 { 25 | ls.position = 0 26 | } 27 | } 28 | 29 | func (ls *lexerStream) canRead() bool { 30 | return ls.position < ls.length 31 | } 32 | -------------------------------------------------------------------------------- /mods/tql/internal/expression/parameter.go: -------------------------------------------------------------------------------- 1 | package expression 2 | 3 | import "fmt" 4 | 5 | // Parameters is a collection of named parameters that can be used by an Expression to retrieve parameters 6 | // when an expression tries to use them. 7 | type Parameters interface { 8 | // Get gets the parameter of the given name, or an error if the parameter is unavailable. 9 | // Failure to find the given parameter should be indicated by returning an error. 10 | Get(name string) (any, error) 11 | } 12 | 13 | type MapParameters map[string]any 14 | 15 | func (p MapParameters) Get(name string) (any, error) { 16 | value, found := p[name] 17 | if !found { 18 | return nil, fmt.Errorf("no parameter '%s' found", name) 19 | } 20 | return value, nil 21 | } 22 | 23 | // sanitizedParameters is a wrapper for Parameters that does sanitization as 24 | // parameters are accessed. 25 | type sanitizedParameters struct { 26 | orig Parameters 27 | } 28 | 29 | func (p sanitizedParameters) Get(key string) (any, error) { 30 | value, err := p.orig.Get(key) 31 | if err != nil { 32 | return nil, err 33 | } 34 | return castToFloat64(value), nil 35 | } 36 | 37 | func castToFloat64(value any) any { 38 | switch v := value.(type) { 39 | case uint8: 40 | return float64(v) 41 | case uint16: 42 | return float64(v) 43 | case uint32: 44 | return float64(v) 45 | case uint64: 46 | return float64(v) 47 | case int8: 48 | return float64(v) 49 | case int16: 50 | return float64(v) 51 | case int32: 52 | return float64(v) 53 | case int64: 54 | return float64(v) 55 | case int: 56 | return float64(v) 57 | case float32: 58 | return float64(v) 59 | } 60 | return value 61 | } 62 | -------------------------------------------------------------------------------- /mods/tql/internal/expression/token.go: -------------------------------------------------------------------------------- 1 | package expression 2 | 3 | type Token struct { 4 | Kind TokenKind 5 | Value any 6 | } 7 | 8 | type TokenKind int 9 | 10 | const ( 11 | UNKNOWN TokenKind = iota 12 | 13 | PREFIX 14 | NUMERIC 15 | BOOLEAN 16 | STRING 17 | PATTERN 18 | TIME 19 | VARIABLE 20 | FUNCTION 21 | SEPARATOR 22 | ACCESSOR 23 | 24 | COMPARATOR 25 | LOGICALOP 26 | MODIFIER 27 | 28 | CLAUSE 29 | CLAUSE_CLOSE 30 | 31 | TERNARY 32 | ) 33 | 34 | func (kind TokenKind) String() string { 35 | switch kind { 36 | case PREFIX: 37 | return "PREFIX" 38 | case NUMERIC: 39 | return "NUMERIC" 40 | case BOOLEAN: 41 | return "BOOLEAN" 42 | case STRING: 43 | return "STRING" 44 | case PATTERN: 45 | return "PATTERN" 46 | case TIME: 47 | return "TIME" 48 | case VARIABLE: 49 | return "VARIABLE" 50 | case FUNCTION: 51 | return "FUNCTION" 52 | case SEPARATOR: 53 | return "SEPARATOR" 54 | case COMPARATOR: 55 | return "COMPARATOR" 56 | case LOGICALOP: 57 | return "LOGICALOP" 58 | case MODIFIER: 59 | return "MODIFIER" 60 | case CLAUSE: 61 | return "CLAUSE" 62 | case CLAUSE_CLOSE: 63 | return "CLAUSE_CLOSE" 64 | case TERNARY: 65 | return "TERNARY" 66 | case ACCESSOR: 67 | return "ACCESSOR" 68 | } 69 | return "UNKNOWN" 70 | } 71 | 72 | type tokenStream struct { 73 | tokens []Token 74 | index int 75 | length int 76 | } 77 | 78 | func newTokenStream(tokens []Token) *tokenStream { 79 | return &tokenStream{ 80 | tokens: tokens, 81 | index: 0, 82 | length: len(tokens), 83 | } 84 | } 85 | 86 | func (ts *tokenStream) rewind() { 87 | ts.index -= 1 88 | } 89 | 90 | func (ts *tokenStream) next() Token { 91 | tok := ts.tokens[ts.index] 92 | ts.index++ 93 | return tok 94 | } 95 | 96 | func (ts *tokenStream) hasNext() bool { 97 | return ts.index < ts.length 98 | } 99 | -------------------------------------------------------------------------------- /mods/tql/loader.go: -------------------------------------------------------------------------------- 1 | package tql 2 | 3 | import ( 4 | "fmt" 5 | "io/fs" 6 | "path/filepath" 7 | "strings" 8 | "time" 9 | 10 | "github.com/machbase/neo-server/v8/mods/util/ssfs" 11 | ) 12 | 13 | type VolatileAssetsProvider interface { 14 | VolatileFilePrefix() string 15 | VolatileFileWrite(name string, val []byte, deadline time.Time) fs.File 16 | } 17 | 18 | type Loader interface { 19 | Load(path string) (*Script, error) 20 | SetVolatileAssetsProvider(vap VolatileAssetsProvider) 21 | } 22 | 23 | type loader struct { 24 | vap VolatileAssetsProvider 25 | } 26 | 27 | func NewLoader() Loader { 28 | return &loader{} 29 | } 30 | 31 | func (ld *loader) Load(path string) (*Script, error) { 32 | var ret *Script 33 | fsmgr := ssfs.Default() 34 | ent, err := fsmgr.Get("/" + strings.TrimPrefix(path, "/")) 35 | if err != nil || ent.IsDir { 36 | return nil, fmt.Errorf("not found '%s'", path) 37 | } 38 | ret = &Script{ 39 | path0: filepath.ToSlash(path), 40 | content: ent.Content, 41 | vap: ld.vap, 42 | } 43 | return ret, nil 44 | } 45 | 46 | func (ld *loader) SetVolatileAssetsProvider(p VolatileAssetsProvider) { 47 | ld.vap = p 48 | } 49 | 50 | type Script struct { 51 | path0 string 52 | content []byte 53 | vap VolatileAssetsProvider 54 | } 55 | 56 | func (sc *Script) String() string { 57 | return fmt.Sprintf("path: %s", sc.path0) 58 | } 59 | -------------------------------------------------------------------------------- /mods/tql/test/TestLoader.csv: -------------------------------------------------------------------------------- 1 | 1 2 | 1.5 3 | 2 4 | 2.5 5 | 3 6 | 3.5 7 | 4 8 | 4.5 9 | 5 10 | 5.5 11 | 6 12 | 6.5 13 | 7 14 | 7.5 15 | 8 16 | 8.5 17 | 9 18 | 9.5 19 | 10 20 | -------------------------------------------------------------------------------- /mods/tql/test/TestLoader.tql: -------------------------------------------------------------------------------- 1 | FAKE( linspace(1,10,19)) 2 | CSV() -------------------------------------------------------------------------------- /mods/tql/test/TestLoader_Pi.csv: -------------------------------------------------------------------------------- 1 | 0.0000,0.0000,0.0000,1.0000 2 | 10.2857,0.1795,0.1786,0.9839 3 | 20.5714,0.3590,0.3514,0.9362 4 | 30.8571,0.5386,0.5129,0.8584 5 | 41.1429,0.7181,0.6579,0.7531 6 | 51.4286,0.8976,0.7818,0.6235 7 | 61.7143,1.0771,0.8806,0.4739 8 | 72.0000,1.2566,0.9511,0.3090 9 | 82.2857,1.4362,0.9909,0.1342 10 | 92.5714,1.6157,0.9990,-0.0449 11 | 102.8571,1.7952,0.9749,-0.2225 12 | 113.1429,1.9747,0.9195,-0.3930 13 | 123.4286,2.1542,0.8346,-0.5509 14 | 133.7143,2.3338,0.7228,-0.6911 15 | 144.0000,2.5133,0.5878,-0.8090 16 | 154.2857,2.6928,0.4339,-0.9010 17 | 164.5714,2.8723,0.2660,-0.9640 18 | 174.8571,3.0518,0.0896,-0.9960 19 | 185.1429,3.2314,-0.0896,-0.9960 20 | 195.4286,3.4109,-0.2660,-0.9640 21 | 205.7143,3.5904,-0.4339,-0.9010 22 | 216.0000,3.7699,-0.5878,-0.8090 23 | 226.2857,3.9494,-0.7228,-0.6911 24 | 236.5714,4.1290,-0.8346,-0.5509 25 | 246.8571,4.3085,-0.9195,-0.3930 26 | 257.1429,4.4880,-0.9749,-0.2225 27 | 267.4286,4.6675,-0.9990,-0.0449 28 | 277.7143,4.8470,-0.9909,0.1342 29 | 288.0000,5.0265,-0.9511,0.3090 30 | 298.2857,5.2061,-0.8806,0.4739 31 | 308.5714,5.3856,-0.7818,0.6235 32 | 318.8571,5.5651,-0.6579,0.7531 33 | 329.1429,5.7446,-0.5129,0.8584 34 | 339.4286,5.9241,-0.3514,0.9362 35 | 349.7143,6.1037,-0.1786,0.9839 36 | 360.0000,6.2832,-0.0000,1.0000 37 | -------------------------------------------------------------------------------- /mods/tql/test/TestLoader_Pi.tql: -------------------------------------------------------------------------------- 1 | FAKE( linspace(0,360, 36)) 2 | MAPVALUE(1, value(0)*(2*PI)/360) 3 | MAPVALUE(2, sin(value(1))) 4 | MAPVALUE(3, cos(value(1))) 5 | CSV(precision(4)) -------------------------------------------------------------------------------- /mods/tql/test/TestLoader_group.csv: -------------------------------------------------------------------------------- 1 | kind,sum,avg,mean 2 | a,6.00,2.00,2.00 3 | b,15.00,5.00,5.00 4 | c,24.00,8.00,8.00 5 | -------------------------------------------------------------------------------- /mods/tql/test/TestLoader_group.tql: -------------------------------------------------------------------------------- 1 | FAKE( json({ 2 | ["a", 1], 3 | ["a", 2], 4 | ["a", 3], 5 | ["b", 4], 6 | ["b", 5], 7 | ["b", 6], 8 | ["c", 7], 9 | ["c", 8], 10 | ["c", 9] 11 | }) ) 12 | 13 | GROUP( lazy(false), by(value(0), "kind"), sum(value(1), "sum"), avg(value(1), "avg"), mean(value(1),"mean")) 14 | 15 | CSV( header(true), precision(2) ) -------------------------------------------------------------------------------- /mods/tql/test/TestLoader_groupbykey.csv: -------------------------------------------------------------------------------- 1 | name,SUM,AVG,MIN,MAX,1st,LAST,RSS,RMS,MEAN,MEDIAN,MEDIAN(INTERP),STDDEV,STDERR,ENTROPY 2 | a,6.00,2.00,1.00,3.00,1.00,3.00,3.74,2.16,2.00,2.00,1.50,1.00,0.58,-4.68 3 | b,15.00,5.00,4.00,6.00,4.00,6.00,8.77,5.07,5.00,5.00,4.50,1.00,0.58,-24.34 4 | c,24.00,8.00,7.00,9.00,7.00,9.00,13.93,8.04,8.00,8.00,7.50,1.00,0.58,-50.03 5 | -------------------------------------------------------------------------------- /mods/tql/test/TestLoader_groupbykey.tql: -------------------------------------------------------------------------------- 1 | FAKE( json({ 2 | ["a", 1], 3 | ["a", 2], 4 | ["a", 3], 5 | ["b", 4], 6 | ["b", 5], 7 | ["b", 6], 8 | ["c", 7], 9 | ["c", 8], 10 | ["c", 9] 11 | }) ) 12 | 13 | GROUP( by(value(0), "name"), 14 | sum(value(1), "SUM"), 15 | avg(value(1), "AVG"), 16 | min(value(1), "MIN"), 17 | max(value(1), "MAX"), 18 | first(value(1), "1st"), 19 | last(value(1), "LAST"), 20 | rss(value(1), "RSS"), 21 | rms(value(1), "RMS"), 22 | mean(value(1), "MEAN"), 23 | median(value(1), "MEDIAN"), 24 | medianInterpolated(value(1), "MEDIAN(INTERP)"), 25 | stddev(value(1), "STDDEV"), 26 | stderr(value(1), "STDERR"), 27 | entropy(value(1), "ENTROPY") 28 | ) 29 | CSV( header(true), precision(2) ) -------------------------------------------------------------------------------- /mods/tql/test/TestLoader_iris.csv: -------------------------------------------------------------------------------- 1 | species,MIN(sepal length),MAX(sepal width),MEDIAN(petal length),AVG(petal width) 2 | Iris-setosa,4.300,4.400,1.500,0.244 3 | Iris-versicolor,4.900,3.400,4.300,1.326 4 | Iris-virginica,4.900,3.800,5.500,2.026 5 | -------------------------------------------------------------------------------- /mods/tql/test/TestLoader_iris.tql: -------------------------------------------------------------------------------- 1 | CSV(file("/iris.data"), 2 | field(0, doubleType(), "sepal length"), 3 | field(1, doubleType(), "sepal width"), 4 | field(2, doubleType(), "petal length"), 5 | field(3, doubleType(), "petal width"), 6 | field(4, stringType(), "species") 7 | ) 8 | GROUP( by(value(4), "species"), 9 | min(value(0), "MIN(sepal length)" ), 10 | max(value(1), "MAX(sepal width)" ), 11 | median(value(2), "MEDIAN(petal length)" ), 12 | avg(value(3), "AVG(petal width)" ) 13 | ) 14 | CSV( heading(true), precision(3) ) 15 | -------------------------------------------------------------------------------- /mods/tql/test/TestLoader_iris_setosa.csv: -------------------------------------------------------------------------------- 1 | GROUP,Min,Median,Avg,Max,StdDev. 2 | Iris-setosa,4.30,5.00,5.01,5.80,0.35 3 | -------------------------------------------------------------------------------- /mods/tql/test/TestLoader_iris_setosa.tql: -------------------------------------------------------------------------------- 1 | CSV( file("/iris.data") ) 2 | FILTER( strToUpper(value(4)) == "IRIS-SETOSA") 3 | GROUP( by(value(4)), 4 | min(value(0), "Min"), 5 | median(value(0), "Median"), 6 | avg(value(0), "Avg"), 7 | max(value(0), "Max"), 8 | stddev(value(0), "StdDev.") 9 | ) 10 | CSV(heading(true), precision(2)) 11 | -------------------------------------------------------------------------------- /mods/tql/test/TestLoader_qq.csv: -------------------------------------------------------------------------------- 1 | text_1,2023-08-18 03:28:58.315,12 2 | text_2,2023-08-18 03:28:58.315,23 3 | text_3,2023-08-18 03:28:58.315,78 4 | text_4,2023-08-18 03:28:58.315,89 5 | text_5,2023-08-18 03:28:58.315,90 6 | -------------------------------------------------------------------------------- /mods/tql/test/TestLoader_qq.tql: -------------------------------------------------------------------------------- 1 | STRING(payload() ?? ` 12345 2 | 23456 3 | 78901 4 | 89012 5 | 90123 6 | `, separator('\n'), trimspace(true)) 7 | // transforming data 8 | FILTER( len(value(0)) > 0 ) 9 | MAPVALUE(-1, time("now")) // equiv. PUSHVALUE(0, time("now")) 10 | MAPVALUE(-1, "text_"+key()) // equiv. PUSHVALUE(0, "text_"+key()) 11 | MAPVALUE(2, strSub( value(2), 0, 2) ) 12 | 13 | // Run this code in the tql editor of web-ui for testing 14 | CSV( timeformat("DEFAULT") ) 15 | // Use APPEND(table('example')) for the real action 16 | // APPEND(table('example')) 17 | -------------------------------------------------------------------------------- /mods/tql/test/TestLoader_simplex.tql: -------------------------------------------------------------------------------- 1 | FAKE( meshgrid( linspace(0, 10, 10), linspace(0, 10, 10)) ) 2 | MAPVALUE(2, abs(simplex(123, value(0), value(1))) * 10) 3 | CSV(precision(3)) 4 | -------------------------------------------------------------------------------- /mods/tql/test/euc-jp.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/machbase/neo-server/93d7c126c0438af5f830eeba6e963de91da9185b/mods/tql/test/euc-jp.csv -------------------------------------------------------------------------------- /mods/tql/test/fft2d.csv: -------------------------------------------------------------------------------- 1 | 1.000100,0.000000 2 | 2.000200,0.000000 3 | 3.000300,0.000000 4 | 4.000400,0.000000 5 | 5.000500,0.000000 6 | 6.000600,0.000000 7 | 7.000700,0.000000 8 | 8.000800,0.000000 9 | 9.000900,0.000000 10 | 10.001000,1.000000 11 | 11.001100,0.000000 12 | 12.001200,0.000000 13 | 13.001300,0.000000 14 | 14.001400,0.000000 15 | 15.001500,0.000001 16 | 16.001600,0.000000 17 | 17.001700,0.000000 18 | 18.001800,0.000000 19 | 19.001900,0.000000 20 | 20.002000,0.000000 21 | 21.002100,0.000001 22 | 22.002200,0.000000 23 | 23.002300,0.000000 24 | 24.002400,0.000000 25 | 25.002500,0.000000 26 | 26.002600,0.000000 27 | 27.002700,0.000000 28 | 28.002800,0.000000 29 | 29.002900,0.000000 30 | 30.003000,0.000000 31 | 31.003100,0.000000 32 | 32.003200,0.000000 33 | 33.003300,0.000000 34 | 34.003400,0.000000 35 | 35.003500,0.000000 36 | 36.003600,0.000000 37 | 37.003700,0.000000 38 | 38.003800,0.000000 39 | 39.003900,0.000000 40 | 40.004000,0.000000 41 | 41.004100,0.000000 42 | 42.004200,0.000000 43 | 43.004300,0.000000 44 | 44.004400,0.000000 45 | 45.004500,0.000000 46 | 46.004600,0.000000 47 | 47.004700,0.000000 48 | 48.004800,0.000000 49 | 49.004900,0.000000 50 | 50.005001,2.000000 51 | 51.005101,0.000000 52 | 52.005201,0.000000 53 | 53.005301,0.000004 54 | 54.005401,0.000000 55 | 55.005501,0.000000 56 | 56.005601,0.000000 57 | 57.005701,0.000000 58 | 58.005801,0.000000 59 | 59.005901,0.000000 60 | -------------------------------------------------------------------------------- /mods/tql/test/html_template_item.html: -------------------------------------------------------------------------------- 1 | {{ define "item" -}} 2 |
  • {{ .id }}: {{ .name }}, {{ .age }}, {{ .address }} 3 | {{- end }} -------------------------------------------------------------------------------- /mods/tql/test/html_template_list.html: -------------------------------------------------------------------------------- 1 | {{- if .IsFirst }}
      {{ end }} 2 | {{ template "item" .V }} 3 | {{ if .IsLast }}
    {{ end -}} -------------------------------------------------------------------------------- /mods/tql/test/iris.data.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/machbase/neo-server/93d7c126c0438af5f830eeba6e963de91da9185b/mods/tql/test/iris.data.gz -------------------------------------------------------------------------------- /mods/tql/test/lines.txt: -------------------------------------------------------------------------------- 1 | line1 2 | line2 3 | 4 | line4 -------------------------------------------------------------------------------- /mods/tql/test/markdown_xhtml.txt: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
    ROWNUMSTRING
    1line1
    2line2
    3
    4line4
    28 |
    -------------------------------------------------------------------------------- /mods/tql/test/oscillator_1.txt: -------------------------------------------------------------------------------- 1 | /r/{"data":{"columns":\["time","value"\],"types":\["datetime","double"\],"rows":\[\[1692329337315327000,0.9169371548618853\],\[1692329337515327000,-0.09615299237813928\],\[1692329337715327000,-0.9763628786653529\],\[1692329337915327000,-0.5072715014883364\],\[1692329338115327000,0.662850914928241\]\]},"success":true,"reason":"success","elapse":".+"} -------------------------------------------------------------------------------- /mods/tql/test/sphere_4_4.csv: -------------------------------------------------------------------------------- 1 | ROWNUM,x,y,z 2 | 1,0.000000,0.000000,1.000000 3 | 2,0.707107,0.000000,0.707107 4 | 3,1.000000,0.000000,0.000000 5 | 4,0.707107,0.000000,-0.707107 6 | 5,0.000000,0.000000,1.000000 7 | 6,0.000000,0.707107,0.707107 8 | 7,0.000000,1.000000,0.000000 9 | 8,0.000000,0.707107,-0.707107 10 | 9,-0.000000,0.000000,1.000000 11 | 10,-0.707107,0.000000,0.707107 12 | 11,-1.000000,0.000000,0.000000 13 | 12,-0.707107,0.000000,-0.707107 14 | 13,-0.000000,-0.000000,1.000000 15 | 14,-0.000000,-0.707107,0.707107 16 | 15,-0.000000,-1.000000,0.000000 17 | 16,-0.000000,-0.707107,-0.707107 18 | -------------------------------------------------------------------------------- /mods/tql/test/sql_ddl_executed.txt: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
    ROWNUMMESSAGE
    1Created successfully.
    16 |
    -------------------------------------------------------------------------------- /mods/tql/test/transpose_all.csv: -------------------------------------------------------------------------------- 1 | N001 2 | 1508806800 3 | 0.1 4 | aa 5 | 0.2 6 | 0.3 7 | N002 8 | 1508806900 9 | 0.4 10 | bb 11 | 0.5 12 | 0.6 13 | N003 14 | 1508807000 15 | 0.7 16 | cc 17 | 0.8 18 | 0.9 19 | -------------------------------------------------------------------------------- /mods/tql/test/transpose_all.tql: -------------------------------------------------------------------------------- 1 | FAKE( csv(strTrimSpace(` 2 | name,time,v1,tail,v2,v3 3 | N001,1508806800,0.1,aa,0.2,0.3 4 | N002,1508806900,0.4,bb,0.5,0.6 5 | N003,1508807000,0.7,cc,0.8,0.9 6 | `))) 7 | 8 | DROP(1) // drop header 9 | 10 | TRANSPOSE() // transpose all fields 11 | 12 | CSV( ) 13 | 14 | -------------------------------------------------------------------------------- /mods/tql/test/transpose_all_hdr.csv: -------------------------------------------------------------------------------- 1 | name,N001 2 | time,1508806800 3 | v1,0.1 4 | tail,aa 5 | v2,0.2 6 | v3,0.3 7 | name,N002 8 | time,1508806900 9 | v1,0.4 10 | tail,bb 11 | v2,0.5 12 | v3,0.6 13 | name,N003 14 | time,1508807000 15 | v1,0.7 16 | tail,cc 17 | v2,0.8 18 | v3,0.9 19 | -------------------------------------------------------------------------------- /mods/tql/test/transpose_all_hdr.tql: -------------------------------------------------------------------------------- 1 | FAKE( csv(strTrimSpace(` 2 | name,time,v1,tail,v2,v3 3 | N001,1508806800,0.1,aa,0.2,0.3 4 | N002,1508806900,0.4,bb,0.5,0.6 5 | N003,1508807000,0.7,cc,0.8,0.9 6 | `))) 7 | 8 | TRANSPOSE(header(true)) // transpose all fields with header 9 | 10 | CSV( ) 11 | 12 | -------------------------------------------------------------------------------- /mods/tql/test/transpose_hdr.csv: -------------------------------------------------------------------------------- 1 | name,time,tail,header,column4 2 | N001,1508806800,aa,v1,0.1 3 | N001,1508806800,aa,v2,0.2 4 | N001,1508806800,aa,v3,0.3 5 | N002,1508806900,bb,v1,0.4 6 | N002,1508806900,bb,v2,0.5 7 | N002,1508806900,bb,v3,0.6 8 | N003,1508807000,cc,v1,0.7 9 | N003,1508807000,cc,v2,0.8 10 | N003,1508807000,cc,v3,0.9 11 | -------------------------------------------------------------------------------- /mods/tql/test/transpose_hdr.tql: -------------------------------------------------------------------------------- 1 | FAKE( csv(strTrimSpace(` 2 | name,time,v1,tail,v2,v3 3 | N001,1508806800,0.1,aa,0.2,0.3 4 | N002,1508806900,0.4,bb,0.5,0.6 5 | N003,1508807000,0.7,cc,0.8,0.9 6 | `))) 7 | 8 | //TRANSPOSE( fixed(0, 1, 3), header(true) ) 9 | // equiv. with 10 | TRANSPOSE( header(true), 2, 4, 5 ) 11 | 12 | MAPVALUE(1, parseTime(value(1), "s", "Local")) 13 | MAPVALUE(4, parseFloat(value(4))) 14 | 15 | CSV( header(true), timeformat("s") ) 16 | 17 | -------------------------------------------------------------------------------- /mods/tql/test/transpose_nohdr.csv: -------------------------------------------------------------------------------- 1 | column0,column1,column2,column3 2 | N001,1508806800,aa,0.1 3 | N001,1508806800,aa,0.2 4 | N001,1508806800,aa,0.3 5 | N002,1508806900,bb,0.4 6 | N002,1508806900,bb,0.5 7 | N002,1508806900,bb,0.6 8 | N003,1508807000,cc,0.7 9 | N003,1508807000,cc,0.8 10 | N003,1508807000,cc,0.9 11 | -------------------------------------------------------------------------------- /mods/tql/test/transpose_nohdr.tql: -------------------------------------------------------------------------------- 1 | FAKE( csv(strTrimSpace(` 2 | name,time,v1,tail,v2,v3 3 | N001,1508806800,0.1,aa,0.2,0.3 4 | N002,1508806900,0.4,bb,0.5,0.6 5 | N003,1508807000,0.7,cc,0.8,0.9 6 | `))) 7 | 8 | DROP(1) 9 | // TRANSPOSE( fixed(0, 1, 3) ) 10 | // equiv. with 11 | TRANSPOSE( 2, 4, 5 ) 12 | 13 | MAPVALUE(1, parseTime(value(1), "s", "Local")) 14 | MAPVALUE(3, parseFloat(value(3))) 15 | 16 | CSV( header(true), timeformat("s") ) 17 | -------------------------------------------------------------------------------- /mods/tql/tql_pragma_test.go: -------------------------------------------------------------------------------- 1 | package tql 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestPragma2(t *testing.T) { 12 | tests := []struct { 13 | Name string 14 | Script string 15 | ExpectFunc func(t *testing.T, task *Task) 16 | }{ 17 | { 18 | Name: "pragma-log-level-bang", 19 | Script: ` 20 | #pragma log-level=warn 21 | FAKE( linspace(1, 5, 5)) 22 | JSON()`, 23 | ExpectFunc: func(t *testing.T, task *Task) { 24 | require.Equal(t, ParseLogLevel("warn"), task.logLevel) 25 | }, 26 | }, 27 | { 28 | Name: "pragma-log-level", 29 | Script: ` 30 | //+ log-level=trace sql-thread-lock 31 | SQL( 'select count(*) from example' ) 32 | JSON()`, 33 | ExpectFunc: func(t *testing.T, task *Task) { 34 | require.Equal(t, ParseLogLevel("trace"), task.logLevel) 35 | require.Equal(t, true, task.nodes[0].PragmaBool(PRAGMA_SQL_THREAD_LOCK)) 36 | }, 37 | }, 38 | { 39 | Name: "pragma-sql-thread-lock-bang", 40 | Script: ` 41 | #pragma sql-thread-lock=0 42 | SQL( 'select count(*) from example' ) 43 | JSON()`, 44 | ExpectFunc: func(t *testing.T, task *Task) { 45 | require.Equal(t, ParseLogLevel("error"), task.logLevel) 46 | require.Equal(t, false, task.nodes[0].PragmaBool(PRAGMA_SQL_THREAD_LOCK)) 47 | }, 48 | }, 49 | } 50 | ctx := context.Background() 51 | 52 | for _, tc := range tests { 53 | t.Run(tc.Name, func(t *testing.T) { 54 | task := NewTaskContext(ctx) 55 | task.SetLogWriter(os.Stdout) 56 | if err := task.CompileString(tc.Script); err != nil { 57 | t.Log("ERROR:", tc.Name, err.Error()) 58 | t.Fail() 59 | return 60 | } 61 | tc.ExpectFunc(t, task) 62 | }) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /mods/util/charset/charset_test.go: -------------------------------------------------------------------------------- 1 | package charset_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/machbase/neo-server/v8/mods/util/charset" 7 | ) 8 | 9 | func TestEncoding(t *testing.T) { 10 | tests := []struct { 11 | charset string 12 | }{ 13 | {"euc-kr"}, 14 | {"euc-jp"}, 15 | } 16 | for _, tt := range tests { 17 | enc, ok := charset.Encoding(tt.charset) 18 | if enc == nil || !ok { 19 | t.Logf("Charset %q failed", tt.charset) 20 | t.Fail() 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /mods/util/defaults.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "reflect" 5 | "strconv" 6 | ) 7 | 8 | // SetDefaultValue set default values for the struct field. 9 | // inspired from: https://github.com/mcuadros/go-defaults 10 | func SetDefaultValue(ptr interface{}) { 11 | elem := reflect.ValueOf(ptr).Elem() 12 | walkField(elem) 13 | } 14 | 15 | func walkField(val reflect.Value) { 16 | t := val.Type() 17 | 18 | for i := 0; i < t.NumField(); i++ { 19 | f := val.Field(i) 20 | if f.Kind() == reflect.Struct { 21 | walkField(f) 22 | } 23 | 24 | if defaultVal := t.Field(i).Tag.Get("default"); defaultVal != "" { 25 | setField(val.Field(i), defaultVal) 26 | } 27 | } 28 | } 29 | 30 | // setField handles String/Bool types only. 31 | func setField(field reflect.Value, defaultVal string) { 32 | switch field.Kind() { 33 | case reflect.String: 34 | if field.String() == "" { 35 | field.Set(reflect.ValueOf(defaultVal).Convert(field.Type())) 36 | } 37 | case reflect.Bool: 38 | if val, err := strconv.ParseBool(defaultVal); err == nil { 39 | field.Set(reflect.ValueOf(val).Convert(field.Type())) 40 | } 41 | case reflect.Int: 42 | if val, err := strconv.ParseInt(defaultVal, 10, 32); err == nil { 43 | field.Set(reflect.ValueOf(val).Convert(field.Type())) 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /mods/util/defaults_test.go: -------------------------------------------------------------------------------- 1 | package util_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/machbase/neo-server/v8/mods/util" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | type Value struct { 11 | StrValue string `default:"string"` 12 | FlagValue bool `default:"true"` 13 | IntValue int `default:"123"` 14 | } 15 | 16 | func TestSetDefault(t *testing.T) { 17 | v := Value{} 18 | util.SetDefaultValue(&v) 19 | 20 | require.Equal(t, "string", v.StrValue) 21 | require.Equal(t, true, v.FlagValue) 22 | require.Equal(t, 123, v.IntValue) 23 | } 24 | -------------------------------------------------------------------------------- /mods/util/files.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "io/fs" 5 | "os" 6 | "path/filepath" 7 | "regexp" 8 | "runtime" 9 | "strings" 10 | ) 11 | 12 | func MkDirIfNotExists(path string) error { 13 | return MkDirIfNotExistsMode(path, 0755) 14 | } 15 | 16 | var windowsDrivePattern = regexp.MustCompile("[A-Za-z]:") 17 | 18 | func MkDirIfNotExistsMode(path string, mode fs.FileMode) error { 19 | sep := string(filepath.Separator) 20 | dirs := strings.Split(path, sep) 21 | if runtime.GOOS != "windows" && strings.HasPrefix(path, sep) { 22 | path = "/" 23 | } else { 24 | path = "" 25 | } 26 | for n, d := range dirs { 27 | if d == "" { 28 | continue 29 | } 30 | if runtime.GOOS == "windows" && n == 0 { 31 | if windowsDrivePattern.MatchString(d) { 32 | path = d + "\\" 33 | continue 34 | } 35 | } 36 | path = filepath.Join(path, d) 37 | _, err := os.Stat(path) 38 | if err != nil && os.IsNotExist(err) { 39 | if err := os.Mkdir(path, mode); err != nil { 40 | return err 41 | } 42 | } else if err != nil { 43 | return err 44 | } 45 | } 46 | return nil 47 | } 48 | -------------------------------------------------------------------------------- /mods/util/format.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "strconv" 5 | 6 | "golang.org/x/text/language" 7 | "golang.org/x/text/message" 8 | ) 9 | 10 | func BytesUnit(v uint64, lang language.Tag) string { 11 | p := message.NewPrinter(lang) 12 | f := float64(v) 13 | u := "" 14 | switch { 15 | case v > 1024*1024*1024*1024: 16 | f = f / (1024 * 1024 * 1024 * 1024) 17 | u = " TB" 18 | case v > 1024*1024*1024: 19 | f = f / (1024 * 1024 * 1024) 20 | u = " GB" 21 | case v > 1024*1024: 22 | f = f / (1024 * 1024) 23 | u = " MB" 24 | case v > 1024: 25 | f = f / 1024 26 | u = " KB" 27 | } 28 | return p.Sprintf("%.1f%s", f, u) 29 | } 30 | 31 | func NumberFormat[T int | uint | int8 | uint8 | int16 | uint16 | int32 | uint32 | int64 | uint64](n T) string { 32 | in := strconv.FormatInt(int64(n), 10) 33 | numOfDigits := len(in) 34 | if n < 0 { 35 | numOfDigits-- // First character is the - sign (not a digit) 36 | } 37 | numOfCommas := (numOfDigits - 1) / 3 38 | 39 | out := make([]byte, len(in)+numOfCommas) 40 | if n < 0 { 41 | in, out[0] = in[1:], '-' 42 | } 43 | 44 | for i, j, k := len(in)-1, len(out)-1, 0; ; i, j = i-1, j-1 { 45 | out[j] = in[i] 46 | if i == 0 { 47 | return string(out) 48 | } 49 | if k++; k == 3 { 50 | j, k = j-1, 0 51 | out[j] = ',' 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /mods/util/format_test.go: -------------------------------------------------------------------------------- 1 | package util_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/machbase/neo-server/v8/mods/util" 7 | "github.com/stretchr/testify/require" 8 | "golang.org/x/text/language" 9 | ) 10 | 11 | type NumberTestCase struct { 12 | v int64 13 | expect string 14 | } 15 | 16 | func TestNumber(t *testing.T) { 17 | tcs := []NumberTestCase{ 18 | {0, "0"}, 19 | {1, "1"}, 20 | {12, "12"}, 21 | {123, "123"}, 22 | {1234, "1,234"}, 23 | {123456789, "123,456,789"}, 24 | {-0, "0"}, 25 | {-1, "-1"}, 26 | {-12, "-12"}, 27 | {-123, "-123"}, 28 | {-1234, "-1,234"}, 29 | {-123456789, "-123,456,789"}, 30 | } 31 | 32 | for _, tc := range tcs { 33 | s := util.NumberFormat(tc.v) 34 | require.Equal(t, tc.expect, s) 35 | } 36 | } 37 | 38 | func TestBytesUnit(t *testing.T) { 39 | var ret string 40 | ret = util.BytesUnit(512, language.German) 41 | require.Equal(t, "512,0", ret) 42 | ret = util.BytesUnit(1024+512, language.Greek) 43 | require.Equal(t, "1,5 KB", ret) 44 | ret = util.BytesUnit((1024+512)*1024, language.BritishEnglish) 45 | require.Equal(t, "1.5 MB", ret) 46 | ret = util.BytesUnit((1024+512)*1024*1024, language.BritishEnglish) 47 | require.Equal(t, "1.5 GB", ret) 48 | ret = util.BytesUnit((1024+512)*1024*1024*1024, language.BritishEnglish) 49 | require.Equal(t, "1.5 TB", ret) 50 | } 51 | -------------------------------------------------------------------------------- /mods/util/interface.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | type InterfaceAddr struct { 8 | Interface string 9 | IP net.IP 10 | Net *net.IPNet 11 | Flags net.Flags 12 | } 13 | 14 | func GetAllAddresses() []*InterfaceAddr { 15 | rt := []*InterfaceAddr{} 16 | ifcs, err := net.Interfaces() 17 | if err != nil { 18 | return rt 19 | } 20 | 21 | for _, ifc := range ifcs { 22 | ifname := ifc.Name 23 | ifaddrs, err := ifc.Addrs() 24 | if err != nil { 25 | continue 26 | } 27 | 28 | for _, addr := range ifaddrs { 29 | netip, netmask, err := net.ParseCIDR(addr.String()) 30 | if err != nil { 31 | continue 32 | } 33 | rt = append(rt, &InterfaceAddr{Interface: ifname, IP: netip, Net: netmask, Flags: ifc.Flags}) 34 | } 35 | } 36 | return rt 37 | } 38 | 39 | func FindAllAddresses(ipaddr net.IP) []*InterfaceAddr { 40 | rt := []*InterfaceAddr{} 41 | 42 | if ipaddr.Equal(net.IPv4zero) { 43 | ifaddrs := GetAllAddresses() 44 | for _, ifa := range ifaddrs { 45 | if ifa.IP.To4() != nil { 46 | rt = append(rt, ifa) 47 | } 48 | } 49 | } else if ipaddr.Equal(net.IPv6zero) { 50 | ifaddrs := GetAllAddresses() 51 | for _, ifa := range ifaddrs { 52 | if ifa.IP.To4() != nil { 53 | rt = append(rt, ifa) 54 | } 55 | } 56 | } else { 57 | ifaddrs := GetAllAddresses() 58 | for _, ifa := range ifaddrs { 59 | if ifa.Net.Contains(ipaddr) { 60 | rt = append(rt, ifa) 61 | } 62 | } 63 | } 64 | 65 | return rt 66 | } 67 | -------------------------------------------------------------------------------- /mods/util/interface_test.go: -------------------------------------------------------------------------------- 1 | package util_test 2 | 3 | import ( 4 | "net" 5 | "testing" 6 | 7 | "github.com/machbase/neo-server/v8/mods/util" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestGetInterfaceAddr(t *testing.T) { 12 | lst := util.GetAllAddresses() 13 | for i, a := range lst { 14 | t.Logf("[%d] %v", i, a) 15 | } 16 | } 17 | 18 | func TestFindAllAddresses(t *testing.T) { 19 | lst := util.FindAllAddresses(net.IPv4(127, 0, 0, 1)) 20 | require.Equal(t, 1, len(lst)) 21 | require.True(t, lst[0].IP.IsLoopback()) 22 | } 23 | 24 | func TestFindAllAddressesAllBind(t *testing.T) { 25 | lst := util.FindAllAddresses(net.IPv4zero) 26 | for i, a := range lst { 27 | t.Logf("[%d] %v", i, a) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /mods/util/jemalloc/jemalloc.go: -------------------------------------------------------------------------------- 1 | //go:build linux && amd64 && debug 2 | // +build linux,amd64,debug 3 | 4 | package jemalloc 5 | 6 | /* 7 | // This cgo directive is what actually causes jemalloc to be linked in to the 8 | // final Go executable 9 | #cgo pkg-config: jemalloc 10 | #include 11 | void _refresh_jemalloc_stats() { 12 | // You just need to pass something not-null into the "epoch" mallctl. 13 | size_t random_something = 1; 14 | mallctl("epoch", NULL, NULL, &random_something, sizeof(random_something)); 15 | } 16 | size_t _get_jemalloc_active() { 17 | size_t stat, stat_size; 18 | stat = 0; 19 | stat_size = sizeof(stat); 20 | mallctl("stats.active", &stat, &stat_size, NULL, 0); 21 | return stat; 22 | } 23 | size_t _get_jemalloc_resident() { 24 | size_t stat, stat_size; 25 | stat = 0; 26 | stat_size = sizeof(stat); 27 | mallctl("stats.resident", &stat, &stat_size, NULL, 0); 28 | return stat; 29 | } 30 | */ 31 | import "C" 32 | 33 | import ( 34 | "sync" 35 | ) 36 | 37 | func init() { 38 | Enabled = true 39 | } 40 | 41 | var refreshLock sync.Mutex 42 | 43 | func HeapStat(stat *Stat) { 44 | refreshLock.Lock() 45 | C._refresh_jemalloc_stats() 46 | if st := C._get_jemalloc_active(); st > 0 { 47 | stat.Active = int64(st) 48 | } 49 | if st := C._get_jemalloc_resident(); st > 0 { 50 | stat.Resident = int64(st) 51 | } 52 | refreshLock.Unlock() 53 | } 54 | -------------------------------------------------------------------------------- /mods/util/jemalloc/jemalloc_none.go: -------------------------------------------------------------------------------- 1 | //go:build !(linux && amd64 && debug) 2 | 3 | package jemalloc 4 | 5 | func HeapStat(stat *Stat) { 6 | } 7 | -------------------------------------------------------------------------------- /mods/util/jemalloc/shared.go: -------------------------------------------------------------------------------- 1 | package jemalloc 2 | 3 | const JMALLOC_STRING = "jemalloc" 4 | 5 | var Enabled bool = false 6 | 7 | type Stat struct { 8 | Active int64 9 | Resident int64 10 | } 11 | -------------------------------------------------------------------------------- /mods/util/mdconv/d2ext/ast.go: -------------------------------------------------------------------------------- 1 | package d2ext 2 | 3 | import ( 4 | "github.com/yuin/goldmark/ast" 5 | "github.com/yuin/goldmark/util" 6 | ) 7 | 8 | type Block struct { 9 | ast.BaseBlock 10 | } 11 | 12 | func (n *Block) IsBlank(source []byte) bool { 13 | for c := n.FirstChild(); c != nil; c = c.NextSibling() { 14 | text := c.(*ast.Text).Segment 15 | if !util.IsBlank(text.Value(source)) { 16 | return false 17 | } 18 | } 19 | return true 20 | } 21 | 22 | func (n *Block) Dump(source []byte, level int) { 23 | ast.DumpHelper(n, source, level, nil, nil) 24 | } 25 | 26 | var KindBlock = ast.NewNodeKind("Block") 27 | 28 | func (n *Block) Kind() ast.NodeKind { 29 | return KindBlock 30 | } 31 | -------------------------------------------------------------------------------- /mods/util/mdconv/d2ext/extender.go: -------------------------------------------------------------------------------- 1 | package d2ext 2 | 3 | import ( 4 | "github.com/yuin/goldmark" 5 | "github.com/yuin/goldmark/parser" 6 | "github.com/yuin/goldmark/renderer" 7 | "github.com/yuin/goldmark/util" 8 | ) 9 | 10 | // waiting for PR merged in "github.com/FurqanSoftware/goldmark-d2" 11 | type Extender struct{} 12 | 13 | func (e *Extender) Extend(m goldmark.Markdown) { 14 | m.Parser().AddOptions(parser.WithASTTransformers( 15 | util.Prioritized(&Transformer{}, 100), 16 | )) 17 | m.Renderer().AddOptions(renderer.WithNodeRenderers( 18 | util.Prioritized(&HTMLRenderer{}, 0), 19 | )) 20 | } 21 | -------------------------------------------------------------------------------- /mods/util/mdconv/d2ext/transformer.go: -------------------------------------------------------------------------------- 1 | package d2ext 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/yuin/goldmark/ast" 7 | "github.com/yuin/goldmark/parser" 8 | "github.com/yuin/goldmark/text" 9 | ) 10 | 11 | type Transformer struct { 12 | } 13 | 14 | var _d2 = []byte("d2") 15 | 16 | func (s *Transformer) Transform(doc *ast.Document, reader text.Reader, pctx parser.Context) { 17 | var blocks []*ast.FencedCodeBlock 18 | 19 | // Collect all blocks to be replaced without modifying the tree. 20 | ast.Walk(doc, func(node ast.Node, enter bool) (ast.WalkStatus, error) { 21 | if !enter { 22 | return ast.WalkContinue, nil 23 | } 24 | 25 | cb, ok := node.(*ast.FencedCodeBlock) 26 | if !ok { 27 | return ast.WalkContinue, nil 28 | } 29 | 30 | lang := cb.Language(reader.Source()) 31 | if !bytes.Equal(lang, _d2) { 32 | return ast.WalkContinue, nil 33 | } 34 | 35 | blocks = append(blocks, cb) 36 | return ast.WalkContinue, nil 37 | }) 38 | 39 | // Nothing to do. 40 | if len(blocks) == 0 { 41 | return 42 | } 43 | 44 | for _, cb := range blocks { 45 | b := new(Block) 46 | b.SetLines(cb.Lines()) 47 | 48 | parent := cb.Parent() 49 | if parent != nil { 50 | parent.ReplaceChild(parent, cb, b) 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /mods/util/mdconv/mdconv.go: -------------------------------------------------------------------------------- 1 | package mdconv 2 | 3 | import ( 4 | "io" 5 | 6 | chromahtml "github.com/alecthomas/chroma/v2/formatters/html" 7 | pikchr "github.com/jchenry/goldmark-pikchr" 8 | "github.com/machbase/neo-server/v8/mods/util/mdconv/d2ext" 9 | "github.com/yuin/goldmark" 10 | highlighting "github.com/yuin/goldmark-highlighting/v2" 11 | "github.com/yuin/goldmark/extension" 12 | "github.com/yuin/goldmark/renderer/html" 13 | "go.abhg.dev/goldmark/mermaid" 14 | ) 15 | 16 | type Converter struct { 17 | darkMode bool 18 | } 19 | 20 | type Option func(*Converter) 21 | 22 | func WithDarkMode(flag bool) Option { 23 | return func(c *Converter) { 24 | c.darkMode = flag 25 | } 26 | } 27 | 28 | func New(opts ...Option) *Converter { 29 | ret := &Converter{} 30 | for _, o := range opts { 31 | o(ret) 32 | } 33 | return ret 34 | } 35 | 36 | func (c *Converter) ConvertString(src string, w io.Writer) error { 37 | return c.Convert([]byte(src), w) 38 | } 39 | 40 | func (c *Converter) Convert(src []byte, w io.Writer) error { 41 | highlightingStyle := "onesenterprise" 42 | if c.darkMode { 43 | highlightingStyle = "catppuccin-macchiato" 44 | } 45 | 46 | md := goldmark.New( 47 | goldmark.WithExtensions( 48 | extension.GFM, 49 | &mermaid.Extender{RenderMode: mermaid.RenderModeClient, NoScript: true}, 50 | &pikchr.Extender{DarkMode: c.darkMode}, 51 | highlighting.NewHighlighting( 52 | highlighting.WithStyle(highlightingStyle), 53 | highlighting.WithFormatOptions( 54 | chromahtml.WithLineNumbers(true), 55 | chromahtml.WrapLongLines(true), 56 | ), 57 | ), 58 | &d2ext.Extender{}, 59 | ), 60 | goldmark.WithRendererOptions( 61 | html.WithXHTML(), 62 | ), 63 | ) 64 | return md.Convert(src, w) 65 | } 66 | -------------------------------------------------------------------------------- /mods/util/mdconv/mdconv_test.go: -------------------------------------------------------------------------------- 1 | package mdconv_test 2 | 3 | import ( 4 | "bytes" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/machbase/neo-server/v8/mods/util/mdconv" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestMdCon(t *testing.T) { 13 | src := `# Test 14 | Content` 15 | expect := `

    Test

    16 |
    Content
    17 | 
    18 | ` 19 | w := &bytes.Buffer{} 20 | conv := mdconv.New(mdconv.WithDarkMode(true)) 21 | err := conv.ConvertString(src, w) 22 | require.Nil(t, err) 23 | require.Equal(t, expect, w.String()) 24 | } 25 | 26 | func TestMdWithImage(t *testing.T) { 27 | code := []string{ 28 | `# Image includes`, 29 | `![sample](./sample_image.png)`, 30 | } 31 | expect := []string{ 32 | `

    Image includes

    `, 33 | `

    sample

    `, 34 | } 35 | 36 | w := &bytes.Buffer{} 37 | conv := mdconv.New(mdconv.WithDarkMode(true)) 38 | err := conv.ConvertString(strings.Join(code, "\n"), w) 39 | require.Nil(t, err) 40 | require.Equal(t, strings.Join(expect, "\n"), strings.TrimSpace(w.String())) 41 | } 42 | -------------------------------------------------------------------------------- /mods/util/metric/counter_test.go: -------------------------------------------------------------------------------- 1 | package metric 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestCounterJSON(t *testing.T) { 11 | c := NewCounter() 12 | c.Add(1.1) 13 | c.Add(2.2) 14 | c.Add(3.3) 15 | 16 | data, err := json.Marshal(c) 17 | require.NoError(t, err) 18 | 19 | expected := `{"samples":3,"value":6.6}` 20 | require.JSONEq(t, expected, string(data)) 21 | 22 | var c2 Counter 23 | err = json.Unmarshal(data, &c2) 24 | require.NoError(t, err) 25 | 26 | require.Equal(t, c.samples, c2.samples) 27 | require.Equal(t, c.value, c2.value) 28 | } 29 | -------------------------------------------------------------------------------- /mods/util/metric/dashboard_test.go: -------------------------------------------------------------------------------- 1 | package metric 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestTrimSeriesNames(t *testing.T) { 10 | series := []Series{ 11 | {Name: "cpu:cpu_user#avg"}, 12 | {Name: "cpu:cpu_system#avg"}, 13 | {Name: "cpu:cpu_idle#avg"}, 14 | } 15 | trimSeriesNames(series) 16 | trimSeriesNames(series) 17 | require.Equal(t, "cpu_user", series[0].Name) 18 | require.Equal(t, "cpu_system", series[1].Name) 19 | require.Equal(t, "cpu_idle", series[2].Name) 20 | 21 | series2 := []Series{ 22 | {Name: "mem:heap_inuse:10s"}, 23 | {Name: "mem:heap_alloc:10s"}, 24 | {Name: "mem:heap_idle:10s"}, 25 | } 26 | trimSeriesNames(series2) 27 | require.Equal(t, "heap_inuse", series2[0].Name) 28 | require.Equal(t, "heap_alloc", series2[1].Name) 29 | require.Equal(t, "heap_idle", series2[2].Name) 30 | 31 | series3 := []Series{ 32 | {Name: "go:goroutines"}, 33 | {Name: "go:threads"}, 34 | } 35 | trimSeriesNames(series3) 36 | require.Equal(t, "goroutines", series3[0].Name) 37 | require.Equal(t, "threads", series3[1].Name) 38 | } 39 | -------------------------------------------------------------------------------- /mods/util/metric/gauge_test.go: -------------------------------------------------------------------------------- 1 | package metric 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestGaugeJSON(t *testing.T) { 11 | g := NewGauge() 12 | g.Add(1.0) 13 | g.Add(2.0) 14 | g.Add(3.0) 15 | 16 | data, err := json.Marshal(g) 17 | require.NoError(t, err) 18 | 19 | expected := `{"samples":3,"sum":6,"value":3}` 20 | require.JSONEq(t, expected, string(data)) 21 | 22 | var g2 Gauge 23 | err = json.Unmarshal(data, &g2) 24 | require.NoError(t, err) 25 | 26 | require.Equal(t, g.samples, g2.samples) 27 | require.Equal(t, g.sum, g2.sum) 28 | require.Equal(t, g.value, g2.value) 29 | } 30 | -------------------------------------------------------------------------------- /mods/util/metric/meter_test.go: -------------------------------------------------------------------------------- 1 | package metric 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestMeterJSON(t *testing.T) { 11 | m := NewMeter() 12 | m.Add(1.0) 13 | m.Add(2.0) 14 | m.Add(3.0) 15 | 16 | data, err := json.Marshal(m) 17 | require.NoError(t, err) 18 | 19 | expected := `{"first":1,"last":3,"min":1,"max":3,"sum":6,"samples":3}` 20 | require.JSONEq(t, expected, string(data)) 21 | 22 | var m2 Meter 23 | err = json.Unmarshal(data, &m2) 24 | require.NoError(t, err) 25 | 26 | require.Equal(t, m.first, m2.first) 27 | require.Equal(t, m.last, m2.last) 28 | require.Equal(t, m.min, m2.min) 29 | require.Equal(t, m.max, m2.max) 30 | require.Equal(t, m.sum, m2.sum) 31 | require.Equal(t, m.samples, m2.samples) 32 | } 33 | -------------------------------------------------------------------------------- /mods/util/metric/metric_test.go: -------------------------------------------------------------------------------- 1 | package metric 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "testing" 7 | "time" 8 | 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestMain(m *testing.M) { 13 | timeZone = time.UTC 14 | m.Run() 15 | } 16 | 17 | func TestMetric(t *testing.T) { 18 | var wg sync.WaitGroup 19 | var out string 20 | var cnt int 21 | var now time.Time 22 | wg.Add(3) 23 | seriesID, err := NewSeriesID("METRIC_1M", "1m/1s", time.Second, 60) 24 | require.NoError(t, err) 25 | c := NewCollector( 26 | WithSamplingInterval(time.Second), 27 | WithSeries(seriesID), 28 | ) 29 | c.AddOutputFunc(func(pd Product) error { 30 | defer wg.Done() 31 | out = fmt.Sprintf("%s %s %v %s %s", 32 | pd.Name, pd.SeriesTitle, pd.Time.Format(time.TimeOnly), pd.Value.String(), pd.Type) 33 | if cnt == 0 { 34 | now = pd.Time 35 | } else { 36 | now = now.Add(time.Second) 37 | } 38 | cnt++ 39 | expect := fmt.Sprintf(`m1:f1 1m/1s %s {"samples":1,"value":1} counter`, now.Format(time.TimeOnly)) 40 | require.Equal(t, expect, out) 41 | return nil 42 | }) 43 | c.AddInputFunc(func(g *Gather) error { 44 | g.Add("m1:f1", 1.0, CounterType(UnitShort)) 45 | return nil 46 | }) 47 | c.Start() 48 | wg.Wait() 49 | 50 | sn, err := c.Inflight("m1:f1") 51 | require.NoError(t, err) 52 | // TODO: how to preserve the lowercase of series ID? 53 | pd := sn["METRIC_1M"] 54 | require.NotNil(t, pd) 55 | require.Equal(t, "m1:f1", pd.Name) 56 | require.Equal(t, int64(1), int64(pd.Value.(*CounterValue).Value)) 57 | require.Equal(t, int64(1), int64(pd.Value.(*CounterValue).Samples)) 58 | require.Equal(t, "counter", pd.Type) 59 | c.Stop() 60 | } 61 | -------------------------------------------------------------------------------- /mods/util/metric/odometer_test.go: -------------------------------------------------------------------------------- 1 | package metric 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestOdometerJSON(t *testing.T) { 11 | om := NewOdometer() 12 | data, err := json.Marshal(om.Produce(true)) 13 | require.NoError(t, err) 14 | 15 | expected := `{"first":0,"last":0, "samples":0}` 16 | require.JSONEq(t, expected, string(data)) 17 | 18 | om = NewOdometer() 19 | om.Add(2.0) 20 | om.Add(7.0) 21 | om.Add(10.0) 22 | 23 | d, _ := om.Produce(false).(*OdometerValue) 24 | require.Equal(t, 8.0, d.Diff()) 25 | 26 | data, err = json.Marshal(om) 27 | require.NoError(t, err) 28 | expected = `{"first":2,"last":10, "samples":3}` 29 | require.JSONEq(t, expected, string(data)) 30 | 31 | om.Produce(true) 32 | om.Add(13.0) 33 | 34 | d, _ = om.Produce(false).(*OdometerValue) 35 | require.Equal(t, 3.0, d.Diff()) 36 | 37 | data, err = json.Marshal(om) 38 | require.NoError(t, err) 39 | expected = `{"first":10,"last":13, "samples":1}` 40 | require.JSONEq(t, expected, string(data)) 41 | 42 | var om2 Odometer 43 | err = json.Unmarshal(data, &om2) 44 | require.NoError(t, err) 45 | 46 | require.Equal(t, om.first, om2.first) 47 | require.Equal(t, om.last, om2.last) 48 | require.Equal(t, om.initialized, om2.initialized) 49 | } 50 | -------------------------------------------------------------------------------- /mods/util/metric/timer_test.go: -------------------------------------------------------------------------------- 1 | package metric 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "testing" 7 | "time" 8 | 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func ExampleTimer() { 13 | timer := &Timer{} 14 | 15 | // Simulate some work 16 | timer.Mark(1100 * time.Millisecond) 17 | 18 | timer.Mark(400 * time.Millisecond) 19 | 20 | s := timer.Produce(false) 21 | fmt.Printf("%+v\n", s) 22 | 23 | // Output: 24 | // 25 | // {"samples":2,"sum":1500000000,"min":400000000,"max":1100000000} 26 | } 27 | 28 | func TestTimer(t *testing.T) { 29 | timer := &Timer{} 30 | 31 | timer.Mark(10 * time.Millisecond) 32 | 33 | timer.Mark(20 * time.Millisecond) 34 | 35 | for i := 3; i <= 100; i++ { 36 | timer.Mark(time.Duration(i*10) * time.Millisecond) 37 | } 38 | require.Equal(t, timer.sumDuration, 50500*time.Millisecond) 39 | require.Equal(t, timer.samples, int64(100)) 40 | require.Equal(t, 10*time.Millisecond, timer.minDuration) 41 | require.Equal(t, 1000*time.Millisecond, timer.maxDuration) 42 | require.Equal(t, `{"samples":100,"sum":50500000000,"min":10000000,"max":1000000000}`, timer.String()) 43 | } 44 | 45 | func TestTimerJSON(t *testing.T) { 46 | tm := NewTimer() 47 | tm.Mark(100 * time.Millisecond) 48 | tm.Mark(200 * time.Millisecond) 49 | tm.Mark(300 * time.Millisecond) 50 | 51 | data, err := json.Marshal(tm) 52 | require.NoError(t, err) 53 | 54 | expected := `{"samples":3,"sum":600000000,"min":100000000,"max":300000000}` 55 | require.JSONEq(t, expected, string(data)) 56 | 57 | var tm2 Timer 58 | err = json.Unmarshal(data, &tm2) 59 | require.NoError(t, err) 60 | 61 | require.Equal(t, tm.samples, tm2.samples) 62 | require.Equal(t, tm.sumDuration, tm2.sumDuration) 63 | require.Equal(t, tm.minDuration, tm2.minDuration) 64 | require.Equal(t, tm.maxDuration, tm2.maxDuration) 65 | } 66 | -------------------------------------------------------------------------------- /mods/util/osutils.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "net" 7 | "os" 8 | "path/filepath" 9 | "strings" 10 | ) 11 | 12 | func MakeListener(addr string) (net.Listener, error) { 13 | if strings.HasPrefix(addr, "unix://") { 14 | pwd, _ := os.Getwd() 15 | if strings.HasPrefix(addr, "unix://../") { 16 | addr = fmt.Sprintf("unix:///%s", filepath.Join(filepath.Dir(pwd), addr[len("unix://../"):])) 17 | } else if strings.HasPrefix(addr, "../") { 18 | addr = fmt.Sprintf("unix:///%s", filepath.Join(filepath.Dir(pwd), addr[len("../"):])) 19 | } else if strings.HasPrefix(addr, "unix://./") { 20 | addr = fmt.Sprintf("unix:///%s", filepath.Join(pwd, addr[len("unix://./"):])) 21 | } else if strings.HasPrefix(addr, "./") { 22 | addr = fmt.Sprintf("unix:///%s", filepath.Join(pwd, addr[len("./"):])) 23 | } else if strings.HasPrefix(addr, "/") { 24 | addr = fmt.Sprintf("unix://%s", addr) 25 | } 26 | path := addr[len("unix://"):] 27 | // delete existing .sock file 28 | if _, err := os.Stat(path); err == nil { 29 | os.Remove(path) 30 | } 31 | return net.Listen("unix", path) 32 | } else if strings.HasPrefix(addr, "tcp://") { 33 | return net.Listen("tcp", addr[len("tcp://"):]) 34 | } else { 35 | return nil, fmt.Errorf("unsupported listen scheme %s", addr) 36 | } 37 | } 38 | 39 | const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 40 | 41 | // RandomString generates a random string of the specified length. 42 | func RandomString(n int) string { 43 | b := make([]byte, n) 44 | for i := range b { 45 | b[i] = letterBytes[rand.Intn(len(letterBytes))] 46 | } 47 | return string(b) 48 | } 49 | -------------------------------------------------------------------------------- /mods/util/osutils_test.go: -------------------------------------------------------------------------------- 1 | package util_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/machbase/neo-server/v8/mods/util" 7 | ) 8 | 9 | func TestRandomString(t *testing.T) { 10 | result := util.RandomString(10) 11 | if len(result) != 10 { 12 | t.Fatal("invalid result, ", result) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /mods/util/osutils_unix.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | package util 4 | 5 | import "fmt" 6 | 7 | func GetWindowsVersion() (majorVersion, miorVersion, buildNumber uint32) { 8 | return 0, 0, 0 9 | } 10 | 11 | func GetTempDirPath() string { 12 | return "/tmp" 13 | } 14 | 15 | func MakeUnixDomainSocketPath(name string) string { 16 | return fmt.Sprintf("unix:///tmp/%s", name) 17 | } 18 | -------------------------------------------------------------------------------- /mods/util/osutils_windows.go: -------------------------------------------------------------------------------- 1 | // go:build windows 2 | package util 3 | 4 | import ( 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | 9 | "golang.org/x/sys/windows" 10 | ) 11 | 12 | func GetWindowsVersion() (majorVersion, minorVersion, buildNumber uint32) { 13 | return windows.RtlGetNtVersionNumbers() 14 | } 15 | 16 | func GetTempDirPath() string { 17 | if tempDir := os.Getenv("TEMP"); tempDir == "" || len(tempDir) >= 100 { 18 | if drive := os.Getenv("SYSTEMDRIVE"); drive != "" { 19 | return drive + "\\" 20 | } else { 21 | return "C:\\" 22 | } 23 | } else { 24 | return tempDir 25 | } 26 | } 27 | 28 | func MakeUnixDomainSocketPath(name string) string { 29 | if tempDir := os.Getenv("TEMP"); tempDir == "" || len(tempDir)+len(name) >= 100 { 30 | if drive := os.Getenv("SYSTEMDRIVE"); drive != "" { 31 | return fmt.Sprintf("unix://%s\\%s", drive, name) 32 | } else { 33 | return fmt.Sprintf("unix://C:\\%s", name) 34 | } 35 | } else { 36 | return fmt.Sprintf("unix://%s", filepath.Join(tempDir, name)) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /mods/util/restclient/test/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "John", 3 | "image": "figure.png", 4 | "doc": "doc.xml" 5 | } 6 | -------------------------------------------------------------------------------- /mods/util/restclient/test/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/machbase/neo-server/93d7c126c0438af5f830eeba6e963de91da9185b/mods/util/restclient/test/1.png -------------------------------------------------------------------------------- /mods/util/restclient/test/1.xml: -------------------------------------------------------------------------------- 1 | 2 | Text -------------------------------------------------------------------------------- /mods/util/scheduler.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "github.com/robfig/cron/v3" 4 | 5 | var defaultCron *cron.Cron 6 | 7 | func DefaultCron() *cron.Cron { 8 | return defaultCron 9 | } 10 | 11 | func SetDefaultCron(cron *cron.Cron) { 12 | if defaultCron != nil { 13 | panic("default cron already set") 14 | } 15 | defaultCron = cron 16 | } 17 | -------------------------------------------------------------------------------- /mods/util/shutdownhook.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "slices" 5 | "sync" 6 | ) 7 | 8 | var shutdownHooks []func() 9 | var shutdownHooksMutex sync.Mutex 10 | 11 | func AddShutdownHook(f func()) { 12 | shutdownHooksMutex.Lock() 13 | shutdownHooks = append(shutdownHooks, f) 14 | shutdownHooksMutex.Unlock() 15 | } 16 | 17 | func RunShutdownHooks() { 18 | shutdownHooksMutex.Lock() 19 | slices.Reverse(shutdownHooks) 20 | for _, f := range shutdownHooks { 21 | f() 22 | } 23 | shutdownHooks = nil 24 | shutdownHooksMutex.Unlock() 25 | } 26 | -------------------------------------------------------------------------------- /mods/util/snowflake/default.go: -------------------------------------------------------------------------------- 1 | package snowflake 2 | 3 | import ( 4 | "strings" 5 | "time" 6 | ) 7 | 8 | var _idGen, _ = NewNode(time.Now().Unix() % 1024) 9 | 10 | func Generate() string { 11 | return strings.ReplaceAll(_idGen.Generate().Base64(), "=", "_") 12 | } 13 | -------------------------------------------------------------------------------- /mods/util/ssfs/test/data1/simple.tql: -------------------------------------------------------------------------------- 1 | INPUT() 2 | 3 | OUTPUT(CSV()) -------------------------------------------------------------------------------- /mods/util/ssfs/test/notaccess.tql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/machbase/neo-server/93d7c126c0438af5f830eeba6e963de91da9185b/mods/util/ssfs/test/notaccess.tql -------------------------------------------------------------------------------- /mods/util/ssfs/test/root/example.tql: -------------------------------------------------------------------------------- 1 | INPUT() 2 | 3 | OUTPUT() -------------------------------------------------------------------------------- /mods/util/ssfs/test/root/hello.sql: -------------------------------------------------------------------------------- 1 | select 2 | time, name, value 3 | from 4 | tag 5 | where 6 | name = 'sensor' 7 | -------------------------------------------------------------------------------- /mods/util/ssfs/test/root/select.sql: -------------------------------------------------------------------------------- 1 | select * from tag; -------------------------------------------------------------------------------- /mods/util/ssfs/virtuals.go: -------------------------------------------------------------------------------- 1 | package ssfs 2 | 3 | import "fmt" 4 | 5 | const urlGitSample = "https://github.com/machbase/neo-tutorials.git" 6 | const enableGitSample = false 7 | 8 | func appendVirtual(path string, entry *Entry) *Entry { 9 | if path == "/" && enableGitSample { 10 | // Add Git Sample repo to root directory 11 | hasSamples := false 12 | for _, child := range entry.Children { 13 | if !child.IsDir || !child.GitClone { 14 | continue 15 | } 16 | switch child.GitUrl { 17 | case urlGitSample: 18 | hasSamples = true 19 | } 20 | } 21 | if !hasSamples { 22 | nameSamples := "Tutorials" 23 | count := 0 24 | reRun: 25 | for _, child := range entry.Children { 26 | if child.Name == nameSamples { 27 | count++ 28 | nameSamples = fmt.Sprintf("Tutorials-%d", count) 29 | goto reRun 30 | } 31 | } 32 | entry.Children = append(entry.Children, &SubEntry{ 33 | IsDir: true, 34 | Name: nameSamples, 35 | Type: "dir", 36 | Size: 0, 37 | LastModifiedMillis: 0, 38 | GitUrl: urlGitSample, 39 | GitClone: true, 40 | Virtual: true, 41 | }) 42 | } 43 | } 44 | return entry 45 | } 46 | -------------------------------------------------------------------------------- /mods/util/strpad.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "math" 5 | "strings" 6 | ) 7 | 8 | // StrPad returns the input string padded on the left, right or both sides 9 | // using padType to the specified padding length padLength. 10 | // 11 | // Example: 12 | // input := "Codes"; 13 | // StrPad(input, 10, " ", "RIGHT") // produces "Codes " 14 | // StrPad(input, 10, "-=", "LEFT") // produces "=-=-=Codes" 15 | // StrPad(input, 10, "_", "BOTH") // produces "__Codes___" 16 | // StrPad(input, 6, "___", "RIGHT") // produces "Codes_" 17 | // StrPad(input, 3, "*", "RIGHT") // produces "Codes" 18 | func StrPad(input string, padLength int, padString string, padType string) string { 19 | var output string 20 | 21 | inputLength := len(input) 22 | padStringLength := len(padString) 23 | 24 | if inputLength >= padLength { 25 | return input 26 | } 27 | 28 | repeat := math.Ceil(float64(1) + (float64(padLength-padStringLength))/float64(padStringLength)) 29 | 30 | switch padType { 31 | case "RIGHT": 32 | output = input + strings.Repeat(padString, int(repeat)) 33 | output = output[:padLength] 34 | case "LEFT": 35 | output = strings.Repeat(padString, int(repeat)) + input 36 | output = output[len(output)-padLength:] 37 | case "BOTH": 38 | length := (float64(padLength - inputLength)) / float64(2) 39 | repeat = math.Ceil(length / float64(padStringLength)) 40 | output = strings.Repeat(padString, int(repeat))[:int(math.Floor(float64(length)))] + input + strings.Repeat(padString, int(repeat))[:int(math.Ceil(float64(length)))] 41 | } 42 | 43 | return output 44 | } 45 | -------------------------------------------------------------------------------- /mods/util/strpad_test.go: -------------------------------------------------------------------------------- 1 | package util_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/machbase/neo-server/v8/mods/util" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestStrPad(t *testing.T) { 11 | input := "Codes" 12 | ret := util.StrPad(input, 10, " ", "RIGHT") 13 | require.Equal(t, "Codes ", ret) 14 | ret = util.StrPad(input, 10, "-=", "LEFT") 15 | require.Equal(t, "=-=-=Codes", ret) 16 | ret = util.StrPad(input, 10, "_", "BOTH") 17 | require.Equal(t, "__Codes___", ret) 18 | ret = util.StrPad(input, 6, "___", "RIGHT") 19 | require.Equal(t, "Codes_", ret) 20 | ret = util.StrPad(input, 3, "*", "RIGHT") 21 | require.Equal(t, "Codes", ret) 22 | 23 | } 24 | -------------------------------------------------------------------------------- /mods/util/testdata/splitter_sql_1.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE abs_table (c1 INTEGER, c2 DOUBLE, c3 VARCHAR(10)); 2 | 3 | INSERT INTO abs_table VALUES(1, 1.0, ''); 4 | 5 | INSERT INTO abs_table VALUES(2, 2.0, 'sqltest'); 6 | 7 | INSERT INTO abs_table VALUES(3, 3.0, 'sqltest'); 8 | 9 | SELECT count(*) - 1 FROM example; 10 | SELECT count(*) -2 FROM example; -- comment 11 | SELECT count(*) / 3 FROM example; 12 | // double slash comment 13 | SELECT count(*) / 4 FROM example; // comment 2 14 | SELECT * FROM example where name = 'contains // slash'; 15 | SELECT * FROM example where name = 'contains -- dash'; -------------------------------------------------------------------------------- /mods/util/testdata/splitter_sql_2.sql: -------------------------------------------------------------------------------- 1 | SELECT 1; SELECT 2 FROM T WHERE name = '--abc'; 2 | -- comment 3 | 4 | SELECT * -- start of statement 5 | FROM 6 | table 7 | WHERE 8 | name = 'a;b--c'; -- end of statement 9 | 10 | -- env: bridge_bad=sqlite 11 | SELECT 4; 12 | -- env: reset 13 | 14 | -- env: bridge=postgres 15 | SELECT 5 FROM T WHERE id = 1; 16 | -- env: bridge=mysql 17 | SELECT 6 FROM T WHERE id = 2; 18 | -- env: reset 19 | 20 | -- env: bridge=ms-sql 21 | SELECT 7 22 | FROM T WHERE id = 3; 23 | -- env: reset 24 | 25 | wrong statement 26 | -------------------------------------------------------------------------------- /mods/util/time_locations_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func ExampleParseTimeLocation() { 9 | tz, _ := ParseTimeLocation("Asia/Seoul", nil) 10 | if tz == nil { 11 | panic("invalid timezone") 12 | } 13 | fmt.Println("Asia/Seoul =", tz.String()) 14 | 15 | tz, _ = ParseTimeLocation("KST", nil) 16 | if tz == nil { 17 | panic("invalid timezone") 18 | } 19 | fmt.Println("KST =", tz.String()) 20 | 21 | tz, _ = ParseTimeLocation("UTC", nil) 22 | if tz == nil { 23 | panic("invalid timezone") 24 | } 25 | fmt.Println("UTC =", tz.String()) 26 | 27 | fallback, err := ParseTimeLocation("America/Invalid", time.UTC) 28 | fmt.Println("Error =", err) 29 | fmt.Println("Fallback =", fallback.String()) 30 | 31 | fallback, err = ParseTimeLocation("Invalid", time.UTC) 32 | fmt.Println("Error =", err) 33 | fmt.Println("Fallback =", fallback.String()) 34 | 35 | // Output: 36 | // Asia/Seoul = Asia/Seoul 37 | // KST = Asia/Seoul 38 | // UTC = UTC 39 | // Error = unknown time zone America/Invalid 40 | // Fallback = UTC 41 | // Error = unknown time zone Invalid 42 | // Fallback = UTC 43 | } 44 | -------------------------------------------------------------------------------- /mods/versions_test.go: -------------------------------------------------------------------------------- 1 | package mods 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestVersion(t *testing.T) { 10 | versionString = "v1.2.3-rc1" 11 | versionGitSHA = "11f32f31" 12 | buildTimestamp = "2023/08/23T11:22" 13 | goVersionString = "1.20.2" 14 | editionString = "standard_edition" 15 | 16 | ver := GetVersion() 17 | require.NotNil(t, ver) 18 | require.Equal(t, 1, ver.Major) 19 | require.Equal(t, 2, ver.Minor) 20 | require.Equal(t, 3, ver.Patch) 21 | require.Equal(t, "11f32f31", ver.GitSHA) 22 | require.Equal(t, "standard_edition", ver.Edition) 23 | require.Equal(t, "v1.2.3-rc1", DisplayVersion()) 24 | require.Equal(t, "v1.2.3-rc1 (11f32f31 2023/08/23T11:22)", VersionString()) 25 | require.Equal(t, "1.20.2", BuildCompiler()) 26 | require.Equal(t, "2023/08/23T11:22", BuildTimestamp()) 27 | require.Equal(t, "standard_edition", Edition()) 28 | } 29 | -------------------------------------------------------------------------------- /scripts/CentOS7.Dockerfile: -------------------------------------------------------------------------------- 1 | ################################################# 2 | ## Build the image with: 3 | # docker build -t centos7-build-env -f ./scripts/CentOS7.Dockerfile . 4 | # 5 | ## Run the container with: 6 | # docker run --rm -v ./tmp:/app/tmp -v ./packages:/app/packages centos7-build-env 7 | # 8 | ################################################# 9 | 10 | FROM centos:7 11 | 12 | COPY ./scripts/CentOS7_repo.txt /etc/yum.repos.d/CentOS-Base.repo 13 | 14 | RUN yum clean all && \ 15 | yum makecache && \ 16 | yum -y update && \ 17 | yum -y group install "Development Tools" && \ 18 | yum -y install wget tar gzip which && \ 19 | echo "Install Go" && \ 20 | wget -L https://go.dev/dl/go1.24.5.linux-amd64.tar.gz -O /tmp/go.tar.gz && \ 21 | tar -C /usr/local -xzf /tmp/go.tar.gz && \ 22 | rm -f /tmp/go.tar.gz && \ 23 | echo 'export PATH=$PATH:/usr/local/go/bin' >> /root/.bashrc 24 | 25 | WORKDIR /app 26 | COPY . /app 27 | 28 | CMD ["/usr/local/go/bin/go", "run", "mage.go", "install-neo-web", "machbase-neo"] 29 | 30 | -------------------------------------------------------------------------------- /scripts/CentOS7_repo.txt: -------------------------------------------------------------------------------- 1 | [base] 2 | name=CentOS-$releasever - Base 3 | baseurl=https://vault.centos.org/7.9.2009/os/x86_64/ 4 | gpgcheck=1 5 | enabled=1 6 | gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 7 | 8 | #released updates 9 | [updates] 10 | name=CentOS-$releasever - Updates 11 | baseurl=https://vault.centos.org/7.9.2009/updates/x86_64/ 12 | gpgcheck=1 13 | enabled=1 14 | gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 15 | 16 | #additional packages that may be useful 17 | [extras] 18 | name=CentOS-$releasever - Extras 19 | baseurl=https://vault.centos.org/7.9.2009/extras/x86_64/ 20 | gpgcheck=0 21 | gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 22 | 23 | #additional packages that extend functionality of existing packages 24 | [centosplus] 25 | name=CentOS-$releasever - Plus 26 | baseurl=https://vault.centos.org/7.9.2009/centosplus/x86_64/ 27 | gpgcheck=1 28 | enabled=0 29 | gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 30 | 31 | [contrib] 32 | name=CentOS-$releasever – Contrib 33 | baseurl=https://vault.centos.org/7.9.2009/contrib/x86_64/ 34 | gpgcheck=1 35 | enabled=0 36 | gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 37 | 38 | [centos-sclo-rh] 39 | name=CentOS-7 - SCLo rh 40 | baseurl=https://vault.centos.org/centos/7.9.2009/sclo/x86_64/rh/ 41 | enabled=1 42 | gpgcheck=0 43 | 44 | [centos-sclo-rh-source] 45 | name=CentOS-7 - SCLo rh Source 46 | baseurl=https://vault.centos.org/centos/7.9.2009/sclo/Source/rh/ 47 | enabled=0 48 | gpgcheck=0 -------------------------------------------------------------------------------- /scripts/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 AS build-stage 2 | ARG TARGETARCH 3 | 4 | COPY . . 5 | 6 | RUN if [ "$TARGETARCH" = "amd64" ]; then \ 7 | cp amd64/machbase-neo /opt/machbase-neo; \ 8 | elif [ "$TARGETARCH" = "arm64" ]; then \ 9 | cp arm64/machbase-neo /opt/machbase-neo; \ 10 | fi 11 | 12 | FROM ubuntu:22.04 AS runtime-stage 13 | 14 | LABEL MAINTAINER="machbase.com " 15 | 16 | RUN apt-get update && apt-get install -y ca-certificates 17 | RUN mkdir -p /file /data /backups 18 | 19 | COPY --from=build-stage /opt/machbase-neo /opt/machbase-neo 20 | 21 | EXPOSE 5652-5656 22 | 23 | VOLUME ["/data", "/file", "/backups"] 24 | 25 | ENTRYPOINT /opt/machbase-neo serve \ 26 | --host 0.0.0.0 \ 27 | --data /data \ 28 | --file /file \ 29 | --backup-dir /backups 30 | 31 | -------------------------------------------------------------------------------- /scripts/Ubuntu18.04.Dockerfile: -------------------------------------------------------------------------------- 1 | ################################################# 2 | ## Build the image with: 3 | # docker build -t ubuntu18-build-env -f ./scripts/Ubuntu18.04.Dockerfile . 4 | # 5 | ## Run the container with: 6 | # docker run --rm -v ./tmp:/app/tmp -v ./packages:/app/packages ubuntu18-build-env 7 | # 8 | ################################################# 9 | 10 | FROM ubuntu:18.04 11 | 12 | RUN apt-get update && \ 13 | apt-get install -y build-essential && \ 14 | apt-get install -y wget curl tar gzip && \ 15 | ARCH=$([ "$(uname -m)" = "aarch64" ] && echo "arm64" || echo "amd64") && \ 16 | echo "Building for architecture: $ARCH" && \ 17 | echo "Install Go" && \ 18 | wget -L https://go.dev/dl/go1.24.5.linux-$ARCH.tar.gz -O /tmp/go.tar.gz && \ 19 | tar -C /usr/local -xzf /tmp/go.tar.gz && \ 20 | rm -f /tmp/go.tar.gz 21 | 22 | ENV PATH="/usr/local/go/bin:${PATH}" 23 | 24 | WORKDIR /app 25 | COPY . /app 26 | 27 | RUN go mod download && \ 28 | go run mage.go install-neo-web 29 | 30 | CMD ["go", "run", "mage.go", "machbase-neo", "package"] 31 | -------------------------------------------------------------------------------- /scripts/Ubuntu24.04.Dockerfile: -------------------------------------------------------------------------------- 1 | ################################################# 2 | ## Build the image with: 3 | # docker build -t ubuntu24-build-env -f ./scripts/Ubuntu24.04.Dockerfile . 4 | # 5 | ## Run the container with: 6 | # docker run --rm -v ./tmp:/app/tmp -v ./packages:/app/packages ubuntu24-build-env 7 | # 8 | ################################################# 9 | 10 | FROM ubuntu:24.04 11 | 12 | RUN apt-get update && \ 13 | apt-get install -y build-essential && \ 14 | apt-get install -y wget curl tar gzip && \ 15 | ARCH=$([ "$(uname -m)" = "aarch64" ] && echo "arm64" || echo "amd64") && \ 16 | echo "Building for architecture: $ARCH" && \ 17 | echo "Install Go" && \ 18 | wget -L https://go.dev/dl/go1.24.5.linux-$ARCH.tar.gz -O /tmp/go.tar.gz && \ 19 | tar -C /usr/local -xzf /tmp/go.tar.gz && \ 20 | rm -f /tmp/go.tar.gz 21 | 22 | ENV PATH="/usr/local/go/bin:${PATH}" 23 | 24 | WORKDIR /app 25 | COPY . /app 26 | 27 | RUN go mod download && \ 28 | go run mage.go install-neo-web 29 | 30 | CMD ["go", "run", "mage.go", "machbase-neo", "package"] 31 | -------------------------------------------------------------------------------- /scripts/Ubuntu_arm32v7.Dockerfile: -------------------------------------------------------------------------------- 1 | ################################################# 2 | ## Prerequisites - qemu 3 | # sudo apt-get update 4 | # sudo apt-get install qemu-system-x86 binfmt-support qemu-user-static 5 | # 6 | ## Build the image with: 7 | # docker build --platform linux/arm/v7 -t ubuntu_arm32v7-build-env -f ./scripts/Ubuntu_arm32v7.Dockerfile . 8 | # 9 | ## Run the container with: 10 | # docker run --rm -v ./tmp:/app/tmp -v ./packages:/app/packages --platform linux/arm/v7 ubuntu_arm32v7-build-env 11 | # 12 | ################################################# 13 | 14 | ## Base Image 15 | # https://hub.docker.com/r/arm32v7/ubuntu/ 16 | # Note: This image is based on Ubuntu 22.04 (Jammy Jellyfish) 17 | # The image is built for ARMv7 architecture (arm32v7) 18 | # The image is suitable for running on ARMv7 devices such as Raspberry Pi 2/3/4, BeagleBone, etc. 19 | FROM arm32v7/ubuntu:jammy 20 | 21 | RUN apt-get update && \ 22 | apt-get install -y build-essential && \ 23 | apt-get install -y wget curl tar gzip && \ 24 | echo "Install Go" && \ 25 | wget -L https://go.dev/dl/go1.24.5.linux-armv6l.tar.gz -O /tmp/go.tar.gz && \ 26 | tar -C /usr/local -xzf /tmp/go.tar.gz && \ 27 | rm -f /tmp/go.tar.gz 28 | 29 | ENV PATH="/usr/local/go/bin:${PATH}" 30 | 31 | WORKDIR /app 32 | COPY . /app 33 | 34 | RUN go mod download && \ 35 | go run mage.go install-neo-web 36 | 37 | CMD ["go", "run", "mage.go", "machbase-neo", "package"] 38 | --------------------------------------------------------------------------------