How can I generate the code-containing comments like mysqldump?












2















I'm needing to duplicate the mysqldump command in Python, and I am trying to figure out how automatically generate the code comments like mysqldump generates.



If you're wondering why I am doing such a stupid thing, it's because I am creating a utility that will make daily dumps of the production server for use in dev environments, but I must anonymize all of the fields that contain private data during the dump. It will also change API keys to test accounts, change passwords for important user accounts, etc. (Obviously this is not our main backup system, but something tailored for use by the devs, so that they can always use a safe snapshot of the production database to work with.)



I'm able to do a lot of this already, but I can't figure out how to automatically generate the correct "code comments" (e.g., "/*!40101 SET @saved_cs_client = @@character_set_client */;") that mysqldump includes before/after the CREATE TABLE statement and the INSERT statements. Obviously I could hard code them, but that just feels wrong, and is potentially short-sighted in terms of portability of code and longevity of use.



Thanks for any ideas you can share!










share|improve this question














bumped to the homepage by Community 1 hour ago


This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.
















  • Can't you tightly integrate C code with Python?

    – Vérace
    Nov 18 '15 at 0:24
















2















I'm needing to duplicate the mysqldump command in Python, and I am trying to figure out how automatically generate the code comments like mysqldump generates.



If you're wondering why I am doing such a stupid thing, it's because I am creating a utility that will make daily dumps of the production server for use in dev environments, but I must anonymize all of the fields that contain private data during the dump. It will also change API keys to test accounts, change passwords for important user accounts, etc. (Obviously this is not our main backup system, but something tailored for use by the devs, so that they can always use a safe snapshot of the production database to work with.)



I'm able to do a lot of this already, but I can't figure out how to automatically generate the correct "code comments" (e.g., "/*!40101 SET @saved_cs_client = @@character_set_client */;") that mysqldump includes before/after the CREATE TABLE statement and the INSERT statements. Obviously I could hard code them, but that just feels wrong, and is potentially short-sighted in terms of portability of code and longevity of use.



Thanks for any ideas you can share!










share|improve this question














bumped to the homepage by Community 1 hour ago


This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.
















  • Can't you tightly integrate C code with Python?

    – Vérace
    Nov 18 '15 at 0:24














2












2








2








I'm needing to duplicate the mysqldump command in Python, and I am trying to figure out how automatically generate the code comments like mysqldump generates.



If you're wondering why I am doing such a stupid thing, it's because I am creating a utility that will make daily dumps of the production server for use in dev environments, but I must anonymize all of the fields that contain private data during the dump. It will also change API keys to test accounts, change passwords for important user accounts, etc. (Obviously this is not our main backup system, but something tailored for use by the devs, so that they can always use a safe snapshot of the production database to work with.)



I'm able to do a lot of this already, but I can't figure out how to automatically generate the correct "code comments" (e.g., "/*!40101 SET @saved_cs_client = @@character_set_client */;") that mysqldump includes before/after the CREATE TABLE statement and the INSERT statements. Obviously I could hard code them, but that just feels wrong, and is potentially short-sighted in terms of portability of code and longevity of use.



Thanks for any ideas you can share!










share|improve this question














I'm needing to duplicate the mysqldump command in Python, and I am trying to figure out how automatically generate the code comments like mysqldump generates.



If you're wondering why I am doing such a stupid thing, it's because I am creating a utility that will make daily dumps of the production server for use in dev environments, but I must anonymize all of the fields that contain private data during the dump. It will also change API keys to test accounts, change passwords for important user accounts, etc. (Obviously this is not our main backup system, but something tailored for use by the devs, so that they can always use a safe snapshot of the production database to work with.)



I'm able to do a lot of this already, but I can't figure out how to automatically generate the correct "code comments" (e.g., "/*!40101 SET @saved_cs_client = @@character_set_client */;") that mysqldump includes before/after the CREATE TABLE statement and the INSERT statements. Obviously I could hard code them, but that just feels wrong, and is potentially short-sighted in terms of portability of code and longevity of use.



Thanks for any ideas you can share!







mysql mysqldump






share|improve this question













share|improve this question











share|improve this question




share|improve this question










asked Nov 17 '15 at 7:44









coreyp_1coreyp_1

1262




1262





bumped to the homepage by Community 1 hour ago


This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.







bumped to the homepage by Community 1 hour ago


This question has answers that may be good or bad; the system has marked it active so that they can be reviewed.















  • Can't you tightly integrate C code with Python?

    – Vérace
    Nov 18 '15 at 0:24



















  • Can't you tightly integrate C code with Python?

    – Vérace
    Nov 18 '15 at 0:24

















Can't you tightly integrate C code with Python?

– Vérace
Nov 18 '15 at 0:24





Can't you tightly integrate C code with Python?

– Vérace
Nov 18 '15 at 0:24










2 Answers
2






active

oldest

votes


















1














Well you can review what mysqldump does on git and try working on it!! Though I'd not go this way...



Few things to suggest:




  • You want to hide column names


--> use --no-create-info to dump only data. (onlydata.sql)



--> Have standard table definitions ready and intact for dev server. (definition.sql)



--> Refreshing dev => load definition.sql and then onlydata.sql




  • Just to note that you can also restrict datadump with --where clauses and even limit that to dump only a few rows.


mysqldump --no-create-info --where " true limit 10000" | gzip > dump.sql.gz



.




  • You want to remove few values from the dump? Well writing tool for searching strings will again cause long time to parse!


--> You might consider setting up a special slave on which you make your changes and then do a full datadump.



--> You can prepare SQL commands to run after dump load finishes as follows:
update secrettable set secretcolumn='xxxxxx' where secretcolumn='reallysecretthing';






share|improve this answer































    1














    First, I assume you understand the basic functionality of "code-containing comments."



    /*!40101 some_sql_statement */


    This means "only execute some_sql_statement if you are a MySQL Server, version 4.01.01 or higher." So, there's no real need to wrap the statements in the comment syntax unless the dump files you are creating are going to handle backwards-compatibility with older versions of MySQL (or you want your backups to look exactly like mysqldump files, which isn't a bad idea). This is why the numbers vary -- the version indicates, for each statement, the version of MySQL server that would understand the statement. This is an attempt to make the files generated by mysqldump to remain as backwards-compatible as possible.



    But, onto the substance of your question.



    Consider the statement you used as an example:



    /*!40101 SET @saved_cs_client = @@character_set_client */;


    This is a bad example, because it actually is static statement you can and should hard-code. From the source code to mysqldump, it's pretty low tech:



    fprintf(sql_file,
    "/*!50003 SET @saved_cs_client = @@character_set_client */ %sn"
    "/*!50003 SET @saved_cs_results = @@character_set_results */ %sn"
    "/*!50003 SET @saved_col_connection = @@collation_connection */ %sn"
    ...


    The %s above is just the statement delimiter (";").



    This is all in switch_cs_variables() inside client/mysqldump.c in the source files.



    The next three lines from the same code use information that mysqldump learned from queries it sent to the server:



          "/*!50003 SET character_set_client  = %s */ %sn"
    "/*!50003 SET character_set_results = %s */ %sn"
    "/*!50003 SET collation_connection = %s */ %sn",


    Looking at the source, the values used here are from character_set_client, character_set_client (again, it's used twice), and collation_connection from the results of of SHOW CREATE [ trigger | function | procedure |event ].



    Indeed, in thinking about this question, I remembered that I once wrote a similar script that generates a partial dump file, using perl. After collecting the data, I write them like this:



    print "SET character_set_client  = $def->{character_set_client};n";
    print "SET character_set_results = $def->{character_set_client};n";
    print "SET collation_connection = $def->{collation_connection};n";


    Note that I didn't bother with the version-specific comment wrapper since I don't have any need to replay these files on any system that is old enough not to recognize these statements.



    Note, also, that the static statements -- that is, the SET @saved... statements ... as well as (later in the dump, not shown above) SET @@... = @saved... statements, are simply there to set the session back to its prior state after each object is created.



    Don't forget that when you're selecting data from the server, MySQL has its own built-in QUOTE() function which will do more correct quoting of string literals (the data you're selecting, for insert) than anything you roll yourself.






    share|improve this answer























      Your Answer








      StackExchange.ready(function() {
      var channelOptions = {
      tags: "".split(" "),
      id: "182"
      };
      initTagRenderer("".split(" "), "".split(" "), channelOptions);

      StackExchange.using("externalEditor", function() {
      // Have to fire editor after snippets, if snippets enabled
      if (StackExchange.settings.snippets.snippetsEnabled) {
      StackExchange.using("snippets", function() {
      createEditor();
      });
      }
      else {
      createEditor();
      }
      });

      function createEditor() {
      StackExchange.prepareEditor({
      heartbeatType: 'answer',
      autoActivateHeartbeat: false,
      convertImagesToLinks: false,
      noModals: true,
      showLowRepImageUploadWarning: true,
      reputationToPostImages: null,
      bindNavPrevention: true,
      postfix: "",
      imageUploader: {
      brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
      contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
      allowUrls: true
      },
      onDemand: true,
      discardSelector: ".discard-answer"
      ,immediatelyShowMarkdownHelp:true
      });


      }
      });














      draft saved

      draft discarded


















      StackExchange.ready(
      function () {
      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fdba.stackexchange.com%2fquestions%2f121306%2fhow-can-i-generate-the-code-containing-comments-like-mysqldump%23new-answer', 'question_page');
      }
      );

      Post as a guest















      Required, but never shown

























      2 Answers
      2






      active

      oldest

      votes








      2 Answers
      2






      active

      oldest

      votes









      active

      oldest

      votes






      active

      oldest

      votes









      1














      Well you can review what mysqldump does on git and try working on it!! Though I'd not go this way...



      Few things to suggest:




      • You want to hide column names


      --> use --no-create-info to dump only data. (onlydata.sql)



      --> Have standard table definitions ready and intact for dev server. (definition.sql)



      --> Refreshing dev => load definition.sql and then onlydata.sql




      • Just to note that you can also restrict datadump with --where clauses and even limit that to dump only a few rows.


      mysqldump --no-create-info --where " true limit 10000" | gzip > dump.sql.gz



      .




      • You want to remove few values from the dump? Well writing tool for searching strings will again cause long time to parse!


      --> You might consider setting up a special slave on which you make your changes and then do a full datadump.



      --> You can prepare SQL commands to run after dump load finishes as follows:
      update secrettable set secretcolumn='xxxxxx' where secretcolumn='reallysecretthing';






      share|improve this answer




























        1














        Well you can review what mysqldump does on git and try working on it!! Though I'd not go this way...



        Few things to suggest:




        • You want to hide column names


        --> use --no-create-info to dump only data. (onlydata.sql)



        --> Have standard table definitions ready and intact for dev server. (definition.sql)



        --> Refreshing dev => load definition.sql and then onlydata.sql




        • Just to note that you can also restrict datadump with --where clauses and even limit that to dump only a few rows.


        mysqldump --no-create-info --where " true limit 10000" | gzip > dump.sql.gz



        .




        • You want to remove few values from the dump? Well writing tool for searching strings will again cause long time to parse!


        --> You might consider setting up a special slave on which you make your changes and then do a full datadump.



        --> You can prepare SQL commands to run after dump load finishes as follows:
        update secrettable set secretcolumn='xxxxxx' where secretcolumn='reallysecretthing';






        share|improve this answer


























          1












          1








          1







          Well you can review what mysqldump does on git and try working on it!! Though I'd not go this way...



          Few things to suggest:




          • You want to hide column names


          --> use --no-create-info to dump only data. (onlydata.sql)



          --> Have standard table definitions ready and intact for dev server. (definition.sql)



          --> Refreshing dev => load definition.sql and then onlydata.sql




          • Just to note that you can also restrict datadump with --where clauses and even limit that to dump only a few rows.


          mysqldump --no-create-info --where " true limit 10000" | gzip > dump.sql.gz



          .




          • You want to remove few values from the dump? Well writing tool for searching strings will again cause long time to parse!


          --> You might consider setting up a special slave on which you make your changes and then do a full datadump.



          --> You can prepare SQL commands to run after dump load finishes as follows:
          update secrettable set secretcolumn='xxxxxx' where secretcolumn='reallysecretthing';






          share|improve this answer













          Well you can review what mysqldump does on git and try working on it!! Though I'd not go this way...



          Few things to suggest:




          • You want to hide column names


          --> use --no-create-info to dump only data. (onlydata.sql)



          --> Have standard table definitions ready and intact for dev server. (definition.sql)



          --> Refreshing dev => load definition.sql and then onlydata.sql




          • Just to note that you can also restrict datadump with --where clauses and even limit that to dump only a few rows.


          mysqldump --no-create-info --where " true limit 10000" | gzip > dump.sql.gz



          .




          • You want to remove few values from the dump? Well writing tool for searching strings will again cause long time to parse!


          --> You might consider setting up a special slave on which you make your changes and then do a full datadump.



          --> You can prepare SQL commands to run after dump load finishes as follows:
          update secrettable set secretcolumn='xxxxxx' where secretcolumn='reallysecretthing';







          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered Nov 17 '15 at 8:05









          mysql_usermysql_user

          1,56468




          1,56468

























              1














              First, I assume you understand the basic functionality of "code-containing comments."



              /*!40101 some_sql_statement */


              This means "only execute some_sql_statement if you are a MySQL Server, version 4.01.01 or higher." So, there's no real need to wrap the statements in the comment syntax unless the dump files you are creating are going to handle backwards-compatibility with older versions of MySQL (or you want your backups to look exactly like mysqldump files, which isn't a bad idea). This is why the numbers vary -- the version indicates, for each statement, the version of MySQL server that would understand the statement. This is an attempt to make the files generated by mysqldump to remain as backwards-compatible as possible.



              But, onto the substance of your question.



              Consider the statement you used as an example:



              /*!40101 SET @saved_cs_client = @@character_set_client */;


              This is a bad example, because it actually is static statement you can and should hard-code. From the source code to mysqldump, it's pretty low tech:



              fprintf(sql_file,
              "/*!50003 SET @saved_cs_client = @@character_set_client */ %sn"
              "/*!50003 SET @saved_cs_results = @@character_set_results */ %sn"
              "/*!50003 SET @saved_col_connection = @@collation_connection */ %sn"
              ...


              The %s above is just the statement delimiter (";").



              This is all in switch_cs_variables() inside client/mysqldump.c in the source files.



              The next three lines from the same code use information that mysqldump learned from queries it sent to the server:



                    "/*!50003 SET character_set_client  = %s */ %sn"
              "/*!50003 SET character_set_results = %s */ %sn"
              "/*!50003 SET collation_connection = %s */ %sn",


              Looking at the source, the values used here are from character_set_client, character_set_client (again, it's used twice), and collation_connection from the results of of SHOW CREATE [ trigger | function | procedure |event ].



              Indeed, in thinking about this question, I remembered that I once wrote a similar script that generates a partial dump file, using perl. After collecting the data, I write them like this:



              print "SET character_set_client  = $def->{character_set_client};n";
              print "SET character_set_results = $def->{character_set_client};n";
              print "SET collation_connection = $def->{collation_connection};n";


              Note that I didn't bother with the version-specific comment wrapper since I don't have any need to replay these files on any system that is old enough not to recognize these statements.



              Note, also, that the static statements -- that is, the SET @saved... statements ... as well as (later in the dump, not shown above) SET @@... = @saved... statements, are simply there to set the session back to its prior state after each object is created.



              Don't forget that when you're selecting data from the server, MySQL has its own built-in QUOTE() function which will do more correct quoting of string literals (the data you're selecting, for insert) than anything you roll yourself.






              share|improve this answer




























                1














                First, I assume you understand the basic functionality of "code-containing comments."



                /*!40101 some_sql_statement */


                This means "only execute some_sql_statement if you are a MySQL Server, version 4.01.01 or higher." So, there's no real need to wrap the statements in the comment syntax unless the dump files you are creating are going to handle backwards-compatibility with older versions of MySQL (or you want your backups to look exactly like mysqldump files, which isn't a bad idea). This is why the numbers vary -- the version indicates, for each statement, the version of MySQL server that would understand the statement. This is an attempt to make the files generated by mysqldump to remain as backwards-compatible as possible.



                But, onto the substance of your question.



                Consider the statement you used as an example:



                /*!40101 SET @saved_cs_client = @@character_set_client */;


                This is a bad example, because it actually is static statement you can and should hard-code. From the source code to mysqldump, it's pretty low tech:



                fprintf(sql_file,
                "/*!50003 SET @saved_cs_client = @@character_set_client */ %sn"
                "/*!50003 SET @saved_cs_results = @@character_set_results */ %sn"
                "/*!50003 SET @saved_col_connection = @@collation_connection */ %sn"
                ...


                The %s above is just the statement delimiter (";").



                This is all in switch_cs_variables() inside client/mysqldump.c in the source files.



                The next three lines from the same code use information that mysqldump learned from queries it sent to the server:



                      "/*!50003 SET character_set_client  = %s */ %sn"
                "/*!50003 SET character_set_results = %s */ %sn"
                "/*!50003 SET collation_connection = %s */ %sn",


                Looking at the source, the values used here are from character_set_client, character_set_client (again, it's used twice), and collation_connection from the results of of SHOW CREATE [ trigger | function | procedure |event ].



                Indeed, in thinking about this question, I remembered that I once wrote a similar script that generates a partial dump file, using perl. After collecting the data, I write them like this:



                print "SET character_set_client  = $def->{character_set_client};n";
                print "SET character_set_results = $def->{character_set_client};n";
                print "SET collation_connection = $def->{collation_connection};n";


                Note that I didn't bother with the version-specific comment wrapper since I don't have any need to replay these files on any system that is old enough not to recognize these statements.



                Note, also, that the static statements -- that is, the SET @saved... statements ... as well as (later in the dump, not shown above) SET @@... = @saved... statements, are simply there to set the session back to its prior state after each object is created.



                Don't forget that when you're selecting data from the server, MySQL has its own built-in QUOTE() function which will do more correct quoting of string literals (the data you're selecting, for insert) than anything you roll yourself.






                share|improve this answer


























                  1












                  1








                  1







                  First, I assume you understand the basic functionality of "code-containing comments."



                  /*!40101 some_sql_statement */


                  This means "only execute some_sql_statement if you are a MySQL Server, version 4.01.01 or higher." So, there's no real need to wrap the statements in the comment syntax unless the dump files you are creating are going to handle backwards-compatibility with older versions of MySQL (or you want your backups to look exactly like mysqldump files, which isn't a bad idea). This is why the numbers vary -- the version indicates, for each statement, the version of MySQL server that would understand the statement. This is an attempt to make the files generated by mysqldump to remain as backwards-compatible as possible.



                  But, onto the substance of your question.



                  Consider the statement you used as an example:



                  /*!40101 SET @saved_cs_client = @@character_set_client */;


                  This is a bad example, because it actually is static statement you can and should hard-code. From the source code to mysqldump, it's pretty low tech:



                  fprintf(sql_file,
                  "/*!50003 SET @saved_cs_client = @@character_set_client */ %sn"
                  "/*!50003 SET @saved_cs_results = @@character_set_results */ %sn"
                  "/*!50003 SET @saved_col_connection = @@collation_connection */ %sn"
                  ...


                  The %s above is just the statement delimiter (";").



                  This is all in switch_cs_variables() inside client/mysqldump.c in the source files.



                  The next three lines from the same code use information that mysqldump learned from queries it sent to the server:



                        "/*!50003 SET character_set_client  = %s */ %sn"
                  "/*!50003 SET character_set_results = %s */ %sn"
                  "/*!50003 SET collation_connection = %s */ %sn",


                  Looking at the source, the values used here are from character_set_client, character_set_client (again, it's used twice), and collation_connection from the results of of SHOW CREATE [ trigger | function | procedure |event ].



                  Indeed, in thinking about this question, I remembered that I once wrote a similar script that generates a partial dump file, using perl. After collecting the data, I write them like this:



                  print "SET character_set_client  = $def->{character_set_client};n";
                  print "SET character_set_results = $def->{character_set_client};n";
                  print "SET collation_connection = $def->{collation_connection};n";


                  Note that I didn't bother with the version-specific comment wrapper since I don't have any need to replay these files on any system that is old enough not to recognize these statements.



                  Note, also, that the static statements -- that is, the SET @saved... statements ... as well as (later in the dump, not shown above) SET @@... = @saved... statements, are simply there to set the session back to its prior state after each object is created.



                  Don't forget that when you're selecting data from the server, MySQL has its own built-in QUOTE() function which will do more correct quoting of string literals (the data you're selecting, for insert) than anything you roll yourself.






                  share|improve this answer













                  First, I assume you understand the basic functionality of "code-containing comments."



                  /*!40101 some_sql_statement */


                  This means "only execute some_sql_statement if you are a MySQL Server, version 4.01.01 or higher." So, there's no real need to wrap the statements in the comment syntax unless the dump files you are creating are going to handle backwards-compatibility with older versions of MySQL (or you want your backups to look exactly like mysqldump files, which isn't a bad idea). This is why the numbers vary -- the version indicates, for each statement, the version of MySQL server that would understand the statement. This is an attempt to make the files generated by mysqldump to remain as backwards-compatible as possible.



                  But, onto the substance of your question.



                  Consider the statement you used as an example:



                  /*!40101 SET @saved_cs_client = @@character_set_client */;


                  This is a bad example, because it actually is static statement you can and should hard-code. From the source code to mysqldump, it's pretty low tech:



                  fprintf(sql_file,
                  "/*!50003 SET @saved_cs_client = @@character_set_client */ %sn"
                  "/*!50003 SET @saved_cs_results = @@character_set_results */ %sn"
                  "/*!50003 SET @saved_col_connection = @@collation_connection */ %sn"
                  ...


                  The %s above is just the statement delimiter (";").



                  This is all in switch_cs_variables() inside client/mysqldump.c in the source files.



                  The next three lines from the same code use information that mysqldump learned from queries it sent to the server:



                        "/*!50003 SET character_set_client  = %s */ %sn"
                  "/*!50003 SET character_set_results = %s */ %sn"
                  "/*!50003 SET collation_connection = %s */ %sn",


                  Looking at the source, the values used here are from character_set_client, character_set_client (again, it's used twice), and collation_connection from the results of of SHOW CREATE [ trigger | function | procedure |event ].



                  Indeed, in thinking about this question, I remembered that I once wrote a similar script that generates a partial dump file, using perl. After collecting the data, I write them like this:



                  print "SET character_set_client  = $def->{character_set_client};n";
                  print "SET character_set_results = $def->{character_set_client};n";
                  print "SET collation_connection = $def->{collation_connection};n";


                  Note that I didn't bother with the version-specific comment wrapper since I don't have any need to replay these files on any system that is old enough not to recognize these statements.



                  Note, also, that the static statements -- that is, the SET @saved... statements ... as well as (later in the dump, not shown above) SET @@... = @saved... statements, are simply there to set the session back to its prior state after each object is created.



                  Don't forget that when you're selecting data from the server, MySQL has its own built-in QUOTE() function which will do more correct quoting of string literals (the data you're selecting, for insert) than anything you roll yourself.







                  share|improve this answer












                  share|improve this answer



                  share|improve this answer










                  answered Nov 17 '15 at 23:50









                  Michael - sqlbotMichael - sqlbot

                  19.2k23261




                  19.2k23261






























                      draft saved

                      draft discarded




















































                      Thanks for contributing an answer to Database Administrators Stack Exchange!


                      • Please be sure to answer the question. Provide details and share your research!

                      But avoid



                      • Asking for help, clarification, or responding to other answers.

                      • Making statements based on opinion; back them up with references or personal experience.


                      To learn more, see our tips on writing great answers.




                      draft saved


                      draft discarded














                      StackExchange.ready(
                      function () {
                      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fdba.stackexchange.com%2fquestions%2f121306%2fhow-can-i-generate-the-code-containing-comments-like-mysqldump%23new-answer', 'question_page');
                      }
                      );

                      Post as a guest















                      Required, but never shown





















































                      Required, but never shown














                      Required, but never shown












                      Required, but never shown







                      Required, but never shown

































                      Required, but never shown














                      Required, but never shown












                      Required, but never shown







                      Required, but never shown







                      Popular posts from this blog

                      الفوسفات في المغرب

                      Four equal circles intersect: What is the area of the small shaded portion and its height

                      جامعة ليفربول