View rendered tutorial (with more details on what this is.)
Run site locally:
bundle exec jekyll serve
Writing a sqlite clone from scratch in C
Home Page: https://cstack.github.io/db_tutorial
License: MIT License
View rendered tutorial (with more details on what this is.)
Run site locally:
bundle exec jekyll serve
I get some errors below when I try to compile the code in "Part 3 - An In-Memory, Append-Only, Singe-Table Database".
...
db.c:38:7: error: unknown type name ‘uint32_t’
db.c:42:7: error: variably modified ‘username’ at file scope
....
This is my environment, gcc version 5.4.0 on ubuntu 16.04.2 LTS.
Could you tell me the gcc version of you ?
Thanks.
environment: ubuntu 18.04 ,gcc version 7.3.0
I had finished the part 3, but when i compile the source code, it reports the following two type errors:
(1) variably modified ‘username’ at file scope
char username[COLUMN_USERNAME_SIZE];
(2) initializer element is not constant
const uint32_t USERNAME_OFFSET = ID_OFFSET + ID_SIZE;
I had google the problem,one question at stackoverflow say that this is because the const just means "read only" not a real constant.
I'm very interested in how can you compile the code successfully.
I have learned a lot from this tutorial, and i thought part 13 will complete this project.
Emm... So can i continue this project? For instance, Part 14?
If you agree, can you give me some advice?
I have translate all parts of this article in chinese on my github pages( I have said the original article is on your website).
😤😤
For the first few parts, when we're reading input to insert statements using scanf
, scanf
will return an error if the first field is not an integer. However in part 4 we stop using scanf
and switch to strtok
. To convert the ID field to an integer, we call atoi
.
The problem is that if atoi
is given a garbage string like "abcdef", it will just return zero, which is a valid ID. With scanf
this would have correctly returned an error.
Additionally, the error messages surrounding IDs are further misleading. One of the tests we write tries to insert with an ID of negative one, and we fix the code to reject IDs that are less than zero. (if (id < 0) {
). However the error message we print says "ID must be positive." This is misleading because 0 is a valid ID in the code, but 0 is not positive.
To fix this, we could replace atoi
with strtol
and check the endptr, and change the error message to "IDs may not be negative." However this would make the code more complex, so I wanted to ask about it in an issue before opening a pull request.
malloc(sizeof(InputBuffer)) should be cast (InputBuffer*)
Hey,
what a nice project, what do you thing about create a repository with the working in progress code in C? I think it would be great as well
Thanks ;)
Hi! Thanks to publish this interesting tutorial. I learned a lot from this tutorial.
BTW, I try to implement this database in Rust (and Python3 for testing). And I wanna publish the source codes on my github account. But the license file is not found in this repository. Could you add a license file? If you don't allow it, I close this issue and I don't publish the source codes.
Thanks.
After implementing B-tree, each node occupies exactly one page of the table, so there is no need to test whether db file is a whole number of pages in pager_open
function.
Pager* pager_open(const char* filename) {
.............
off_t file_length = lseek(fd, 0, SEEK_END);
Pager* pager = malloc(sizeof(Pager));
pager->file_descriptor = fd;
pager->file_length = file_length;
pager->num_pages = (file_length / PAGE_SIZE);
// if (file_length % PAGE_SIZE != 0) {
// printf("Db file is not a whole number of pages. Corrupt file.\n");
// exit(EXIT_FAILURE);
// }
for (uint32_t i = 0; i < TABLE_MAX_PAGES; i++) {
pager->pages[i] = NULL;
}
@cstack To be honest, this tutorial is awesome, I really hope it can be continued. Can you introduce the next plan or what contents of the next? Just like Part 14. Splitting the Internal Nodes? And I found this: https://play.google.com/books/reader?id=9Z6IQQnX1JEC&hl=zh_CN&pg=GBS.PR9, I mean, we still have many works to do, right? If I want to continued that, what should I do, can you give some advice? Thanks a lot.
Hello, the test ruby script I wrote according to your article cannot run, can you tell me the solution
My gcc version on Windows is: version 8.1.0 (i686-posix-dwarf-rev0, Built by MinGW-W64 project).
I tried to add define _GNU_SOURCE as some websites have suggested, but it wound't work.
Could someone help me with this? Thanks.
When I compile part3 branch, I got the following errors:
make
gcc db.c -o db
db.c:32:7: error: unknown type name ‘uint32_t’
const uint32_t COLUMN_USERNAME_SIZE = 32;
^
db.c:33:7: error: unknown type name ‘uint32_t’
const uint32_t COLUMN_EMAIL_SIZE = 255;
^
db.c:35:3: error: unknown type name ‘uint32_t’
uint32_t id;
^
db.c:36:8: error: variably modified ‘username’ at file scope
char username[COLUMN_USERNAME_SIZE];
^
db.c:37:8: error: variably modified ‘email’ at file scope
char email[COLUMN_EMAIL_SIZE];
^
db.c:47:7: error: unknown type name ‘uint32_t’
const uint32_t ID_SIZE = sizeof(((Row*)0)->id);
^
db.c:48:7: error: unknown type name ‘uint32_t’
const uint32_t USERNAME_SIZE = sizeof(((Row*)0)->username);
^
db.c:49:7: error: unknown type name ‘uint32_t’
const uint32_t EMAIL_SIZE = sizeof(((Row*)0)->email);
^
db.c:50:7: error: unknown type name ‘uint32_t’
const uint32_t ID_OFFSET = 0;
^
db.c:51:7: error: unknown type name ‘uint32_t’
const uint32_t USERNAME_OFFSET = ID_OFFSET + ID_SIZE;
^
db.c:51:34: error: initializer element is not constant
const uint32_t USERNAME_OFFSET = ID_OFFSET + ID_SIZE;
^
db.c:52:7: error: unknown type name ‘uint32_t’
const uint32_t EMAIL_OFFSET = USERNAME_OFFSET + USERNAME_SIZE;
^
db.c:52:31: error: initializer element is not constant
const uint32_t EMAIL_OFFSET = USERNAME_OFFSET + USERNAME_SIZE;
^
db.c:53:7: error: unknown type name ‘uint32_t’
const uint32_t ROW_SIZE = ID_SIZE + USERNAME_SIZE + EMAIL_SIZE;
^
db.c:53:27: error: initializer element is not constant
const uint32_t ROW_SIZE = ID_SIZE + USERNAME_SIZE + EMAIL_SIZE;
^
db.c:55:7: error: unknown type name ‘uint32_t’
const uint32_t PAGE_SIZE = 4096;
^
db.c:56:7: error: unknown type name ‘uint32_t’
const uint32_t TABLE_MAX_PAGES = 100;
^
db.c:57:7: error: unknown type name ‘uint32_t’
const uint32_t ROWS_PER_PAGE = PAGE_SIZE / ROW_SIZE;
^
db.c:57:32: error: initializer element is not constant
const uint32_t ROWS_PER_PAGE = PAGE_SIZE / ROW_SIZE;
^
db.c:58:7: error: unknown type name ‘uint32_t’
const uint32_t TABLE_MAX_ROWS = ROWS_PER_PAGE * TABLE_MAX_PAGES;
^
db.c:58:33: error: initializer element is not constant
const uint32_t TABLE_MAX_ROWS = ROWS_PER_PAGE * TABLE_MAX_PAGES;
^
db.c:61:9: error: variably modified ‘pages’ at file scope
void* pages[TABLE_MAX_PAGES];
^
db.c:62:3: error: unknown type name ‘uint32_t’
uint32_t num_rows;
^
db.c:82:30: error: unknown type name ‘uint32_t’
void* row_slot(Table* table, uint32_t row_num) {
^
db.c: In function ‘execute_insert’:
db.c:161:32: warning: implicit declaration of function ‘row_slot’ [-Wimplicit-function-declaration]
serialize_row(row_to_insert, row_slot(table, table->num_rows));
^
db.c:161:32: warning: passing argument 2 of ‘serialize_row’ makes pointer from integer without a cast [-Wint-conversion]
db.c:70:6: note: expected ‘void *’ but argument is of type ‘int’
void serialize_row(Row* source, void* destination) {
^
db.c: In function ‘execute_select’:
db.c:169:8: error: unknown type name ‘uint32_t’
for (uint32_t i = 0; i < table->num_rows; i++) {
^
db.c:170:21: warning: passing argument 1 of ‘deserialize_row’ makes pointer from integer without a cast [-Wint-conversion]
deserialize_row(row_slot(table, i), &row);
^
db.c:76:6: note: expected ‘void *’ but argument is of type ‘int’
void deserialize_row(void* source, Row* destination) {
^
Makefile:2: recipe for target 'db' failed
make: *** [db] Error 1
According to the errors, I found the uint32_t
type is unknown type, we should add stdint.h
header file.
The errors about variably modified ‘xxx’ at file scope
can be resolved by exchanging the const
variables to macro. For example:
Change the following codes
const uint32_t ID_SIZE = sizeof(((Row*)0)->id);
const uint32_t USERNAME_SIZE = sizeof(((Row*)0)->username);
const uint32_t EMAIL_SIZE = sizeof(((Row*)0)->email);
const uint32_t ID_OFFSET = 0;
const uint32_t USERNAME_OFFSET = ID_OFFSET + ID_SIZE;
const uint32_t EMAIL_OFFSET = USERNAME_OFFSET + USERNAME_SIZE;
const uint32_t ROW_SIZE = ID_SIZE + USERNAME_SIZE + EMAIL_SIZE;
to
#define ID_SIZE sizeof(((Row*)0)->id)
#define USERNAME_SIZE sizeof(((Row*)0)->username)
#define EMAIL_SIZE sizeof(((Row*)0)->email)
#define ID_OFFSET 0
#define USERNAME_OFFSET (ID_OFFSET + ID_SIZE)
#define EMAIL_OFFSET (USERNAME_OFFSET + USERNAME_SIZE)
#define ROW_SIZE (ID_SIZE + USERNAME_SIZE + EMAIL_SIZE)
Here is my gcc version
$ gcc --version
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
what if the code is also stored per part,
so that it makes it easier to validate the correctness of program per part tutorial?
btw, thanks for the very nice tutorial 👍
Thanks for doing that, i will be following along
Hello, this is a really helpful project to me especially the tutorial. I am a Chinese native speaker,so I want to translate this awesome tutorial to Chinese inorder to share to more Chinese student such as me. Should I create a new repository or new pull requests
? Thank for your advice.
According to the code in line uint32_t index = ( one_past_max_index+min_index ) / 2; // ?? overflow
it seems possible to have an overflow because according to binary search algorithm there is possibility of having an overflow when using the formular m=(l+r)/2
instead of this formula m=l+(r-l)/2
source : https://medium.com/swlh/overflow-bug-in-binary-search-c4d4a824807a
Cursor* leaf_node_find(Table* table, uint32_t page_num, uint32_t key)
{
// code ....
while (one_past_max_index != min_index) {
uint32_t index = ( one_past_max_index+min_index ) / 2; // ?? overflow
uint32_t key_at_index = *leaf_node_key(node, index);
if (key == key_at_index) {
// set the cursor to the current position before returning it
cursor->cell_num = index;
return cursor;
}
// follow the normal binary search algorithm based
// on where the condition fall to whether mid-1 of mid + 1
if (key < key_at_index) {
one_past_max_index = index;
} else {
min_index = index + 1;
}
}
}
or what do you think ?
Hi,
I was wondering how you would stock, in the same file, multiple tables (with the same or different fields, it doesnt matter) ?
Any idea of an implementation ?
Hi, there! Great work! But I found a bug:
uint32_t *internal_node_key(void *node, uint32_t key_num)
{
return internal_node_cell(node, key_num) + INTERNAL_NODE_CHILD_SIZE;
}
should be
uint32_t *internal_node_key(void *node, uint32_t key_num)
{
return internal_node_cell(node, key_num) + INTERNAL_NODE_CHILD_SIZE/sizeof(uint32_t);
}
Otherwise, the position of max key would get a 12 bytes offset compared to Internal Node Header Layout because the internal_node_cell() returns a uint32_t pointer, which means, when we plus INTERNAL_NODE_CHILD_SIZE(4 bytes) to it, it would be 4*4 =16bytes.
Is it to copy the row_to_insert pointer to new pointer?
@cstack Could I translate these articles into Chinese?
a
In actuality, we can’t store a full 4 KB of data per leaf node due to the overhead of the header, keys, and wasted space. But we can search through something like 500 GB of data by loading only 4 pages from disk. This is why the B-Tree is a useful data structure for databases.
why 4 pages can search 500GB data?
In my opinion 4 pages just can search 8M data.
4 * 512 * 4 = 8M
Do I misunderstand it?
Thanks in advance.
I have reached section 4, and its already hurting my brain how many assumptions this tutorial has on the pre requisites we need.
RSS Feed only contain posts 1-10 and not the latest 10 posts.
I think your project is very good and deserves to be understood by more people. Can I translate it into Chinese and make it know by more people?
PrepareResult prepare_statement(InputBuffer* input_buffer,
Statement* statement) {
if (strncmp(input_buffer->buffer, "insert", 6) == 0) {
statement->type = STATEMENT_INSERT;
return PREPARE_SUCCESS;
}
if (strcmp(input_buffer->buffer, "select") == 0) {
statement->type = STATEMENT_SELECT;
return PREPARE_SUCCESS;
}
return PREPARE_UNRECOGNIZED_STATEMENT;
}
I cannot find definition of S_IWUSR
and S_IRUSR
until I add #include <sys/stat.h>
If page_num start from 0 to TABLE_MAX_PAGE - 1, not 1, the if statement should be this instead. Tell me if I am right or wrong.
void* get_page(Pager* pager, uint32_t page_number){
if(page_number >= TABLE_MAX_PAGES){
printf("Tried to fetch page number out of bounds. %d > %d\n", page_number, TABLE_MAX_PAGES);
exit(EXIT_FAILURE);
}
I have read the whole tutorial, I don't know C language and want to clarify, did the author opened the file and turned all of the data inside into a BTree in memory?
Should we create a folder for uploading code of each parts It help to copy and read code easier or someone can jump around with code.
First of all thank you for this tutorial. Just fantastic. I have a question for you. From tutorial we directly insert the table inside the code and make insertions and more. But if i wanted to create a table through the "CREATE TABLE" command you would have some advice on how to do it?
Looks like a second argument is required for calloc
. In the tutorial, we have:
Table* table = calloc(sizeof(Table));
which results in a compilation error:
main.c:96:38: error: too few arguments to function call, expected 2, have 1
Table* table = calloc(sizeof(Table));
~~~~~~ ^
I think it should be changed to:
Table* table = calloc(sizeof(Table), TABLE_MAX_PAGES * PAGE_SIZE);
From https://cstack.github.io/db_tutorial/parts/part8.html :
+ // Make room for new cell
+ for (uint32_t i = num_cells; i > cursor->cell_num; i--) {
+ memcpy(leaf_node_cell(node, i), leaf_node_cell(node, i - 1),
+ LEAF_NODE_CELL_SIZE);
+ }
Why call piecewise memcpy() gazillion of times, if one memmove() would be enough?
Report generated by Google Search Console:
Mobile Usability issues detected on https://cstack.github.io/db_tutorial/
To the owner of https://cstack.github.io/db_tutorial/:
Search Console has identified that your site is affected by 3 Mobile Usability issues:
Top Issues
The following issues were found on your site:
Text too small to read
Clickable elements too close together
Content wider than screen
We recommend that you fix these issues when possible to enable the best experience and coverage in Google Search.
void serialize_row(Row* source, void* destination) {
memcpy(destination + ID_OFFSET, &(source->id), ID_SIZE);
memcpy(destination + USERNAME_OFFSET, &(source->username), USERNAME_SIZE);
memcpy(destination + EMAIL_OFFSET, &(source->email), EMAIL_SIZE);
}
these code confused me.
memcpy(destination + USERNAME_OFFSET, &(source->username), USERNAME_SIZE);
when the input is "insert 1 csstack [email protected] " the length of Row.username is 7, but the args for memcpy copy size is USERNAME_SIZE, which is 32. Is there is some problem? The size of the given username(csstack) is smaller than the USER_SIZE. This may be copy some bytes that doesn't need. Same problem for the EMAIL_SIZE. I was blocked by these code. Could you help me? Thanks a lot.
After reading this tutorial, I notice that the tutorial seems not over yet.
So will you finish the remaining part?
void deserialize_row(void* source, Row* destination) {
memcpy(&(destination->id), source + ID_OFFSET, ID_SIZE);
memcpy(&(destination->username), source + USERNAME_OFFSET, USERNAME_SIZE);
memcpy(&(destination->email), source + EMAIL_OFFSET, EMAIL_SIZE);
}
We know that the clang stirng was terminated by '\0'. Copy the entire buffer in the deserialize_row method. char username[USERNAME_SIZE] in Row is csstack, username[7] was not set '\0' explicitly. If the value of username[7] is not '\0', when execute 'select', this may be wrong.
I can't write proper code, my platform is ubuntu18.04. Is there someone can provide the code running properly on Ubuntu? Thanks a lot.
implicit declaration of function 'getline'; did you mean 'getenv'? [-Wimplicit-function-declaration]gcc
Hello, I'm still a learner of the c programming language and database. The below line has uint8_t
but the definitions above it are all uint32_t
, I think they should use the same def, all uint8_t
or all uint32_t
, is there any detail here I didn't notice?
Line 96 in 051b5c0
error: initializer element is not constant
const uint32_t USERNAME_OFFSET = ID_OFFSET + ID_SIZE;
^~~~~~~~~
/cygdrive/c/Users/RJ/CLionProjects/DB Minor/rj/db.c:56:31: error: initializer element is not constant
const uint32_t EMAIL_OFFSET = USERNAME_OFFSET + USERNAME_SIZE;
^~~~~~~~~~~~~~~
/cygdrive/c/Users/RJ/CLionProjects/DB Minor/rj/db.c:57:27: error: initializer element is not constant
const uint32_t ROW_SIZE = ID_SIZE + USERNAME_SIZE + EMAIL_SIZE;
^~~~~~~
/cygdrive/c/Users/RJ/CLionProjects/DB Minor/rj/db.c:93:33: error: initializer element is not constant
const uint32_t IS_ROOT_OFFSET = NODE_TYPE_SIZE;
^~~~~~~~~~~~~~
/cygdrive/c/Users/RJ/CLionProjects/DB Minor/rj/db.c:95:40: error: initializer element is not constant
const uint32_t PARENT_POINTER_OFFSET = IS_ROOT_OFFSET + IS_ROOT_SIZE;
^~~~~~~~~~~~~~
/cygdrive/c/Users/RJ/CLionProjects/DB Minor/rj/db.c:97:5: error: initializer element is not constant
NODE_TYPE_SIZE + IS_ROOT_SIZE + PARENT_POINTER_SIZE;
^~~~~~~~~~~~~~
/cygdrive/c/Users/RJ/CLionProjects/DB Minor/rj/db.c:103:48: error: initializer element is not constant
const uint32_t INTERNAL_NODE_NUM_KEYS_OFFSET = COMMON_NODE_HEADER_SIZE;
^~~~~~~~~~~~~~~~~~~~~~~
/cygdrive/c/Users/RJ/CLionProjects/DB Minor/rj/db.c:106:5: error: initializer element is not constant
INTERNAL_NODE_NUM_KEYS_OFFSET + INTERNAL_NODE_NUM_KEYS_SIZE;
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/cygdrive/c/Users/RJ/CLionProjects/DB Minor/rj/db.c:107:44: error: initializer element is not constant
const uint32_t INTERNAL_NODE_HEADER_SIZE = COMMON_NODE_HEADER_SIZE +
^~~~~~~~~~~~~~~~~~~~~~~
/cygdrive/c/Users/RJ/CLionProjects/DB Minor/rj/db.c:117:5: error: initializer element is not constant
INTERNAL_NODE_CHILD_SIZE + INTERNAL_NODE_KEY_SIZE;
^~~~~~~~~~~~~~~~~~~~~~~~
/cygdrive/c/Users/RJ/CLionProjects/DB Minor/rj/db.c:125:45: error: initializer element is not constant
const uint32_t LEAF_NODE_NUM_CELLS_OFFSET = COMMON_NODE_HEADER_SIZE;
^~~~~~~~~~~~~~~~~~~~~~~
/cygdrive/c/Users/RJ/CLionProjects/DB Minor/rj/db.c:128:5: error: initializer element is not constant
LEAF_NODE_NUM_CELLS_OFFSET + LEAF_NODE_NUM_CELLS_SIZE;
^~~~~~~~~~~~~~~~~~~~~~~~~~
/cygdrive/c/Users/RJ/CLionProjects/DB Minor/rj/db.c:129:40: error: initializer element is not constant
const uint32_t LEAF_NODE_HEADER_SIZE = COMMON_NODE_HEADER_SIZE +
^~~~~~~~~~~~~~~~~~~~~~~
/cygdrive/c/Users/RJ/CLionProjects/DB Minor/rj/db.c:138:39: error: initializer element is not constant
const uint32_t LEAF_NODE_VALUE_SIZE = ROW_SIZE;
^~~~~~~~
/cygdrive/c/Users/RJ/CLionProjects/DB Minor/rj/db.c:140:5: error: initializer element is not constant
LEAF_NODE_KEY_OFFSET + LEAF_NODE_KEY_SIZE;
^~~~~~~~~~~~~~~~~~~~
/cygdrive/c/Users/RJ/CLionProjects/DB Minor/rj/db.c:141:38: error: initializer element is not constant
const uint32_t LEAF_NODE_CELL_SIZE = LEAF_NODE_KEY_SIZE + LEAF_NODE_VALUE_SIZE;
^~~~~~~~~~~~~~~~~~
/cygdrive/c/Users/RJ/CLionProjects/DB Minor/rj/db.c:142:44: error: initializer element is not constant
const uint32_t LEAF_NODE_SPACE_FOR_CELLS = PAGE_SIZE - LEAF_NODE_HEADER_SIZE;
^~~~~~~~~
/cygdrive/c/Users/RJ/CLionProjects/DB Minor/rj/db.c:144:5: error: initializer element is not constant
LEAF_NODE_SPACE_FOR_CELLS / LEAF_NODE_CELL_SIZE;
^~~~~~~~~~~~~~~~~~~~~~~~~
/cygdrive/c/Users/RJ/CLionProjects/DB Minor/rj/db.c:145:46: error: initializer element is not constant
const uint32_t LEAF_NODE_RIGHT_SPLIT_COUNT = (LEAF_NODE_MAX_CELLS + 1) / 2;
^
/cygdrive/c/Users/RJ/CLionProjects/DB Minor/rj/db.c:147:5: error: initializer element is not constant
(LEAF_NODE_MAX_CELLS + 1) - LEAF_NODE_RIGHT_SPLIT_COUNT;
^
make[3]: *** [CMakeFiles/db.dir/build.make:63: CMakeFiles/db.dir/rj/db.c.o] Error 1
make[2]: *** [CMakeFiles/Makefile2:73: CMakeFiles/db.dir/all] Error 2
make[1]: *** [CMakeFiles/Makefile2:85: CMakeFiles/db.dir/rule] Error 2
make: *** [Makefile:118: db] Error 2
Hi, I was wondering if you had any intention of finishing the series? I've followed along all the way through part 13 and found it very helpful and educational, and was hoping to finish the series but I found no steps after part 13.
I presume you have a lot of coding experience. I don't know why but you append all the names of your structures with _t, example:
enum MetaCommandResult_t {
META_COMMAND_SUCCESS,
META_COMMAND_UNRECOGNIZED_COMMAND
};
typedef enum MetaCommandResult_t MetaCommandResult;
and then later redefine it.
Is there any advantage to doing this?
Due to the crazy formatting on your web page, I have roughly 80% white space on each line and 20% code. So of course I have to scroll sideways to read the code.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.